Skip to content

Commit 298b12f

Browse files
committed
Fix 6323: allow compression of the configMap
Add the `--gzip-configmap=true` CLI parameter in order to compress the configMap, if it exeeds the max length. Signed-off-by: Nahshon Unna-Tsameret <[email protected]>
1 parent af14062 commit 298b12f

File tree

4 files changed

+555
-112
lines changed

4 files changed

+555
-112
lines changed

internal/olm/operator/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Configuration struct {
3737
Client client.Client
3838
Scheme *runtime.Scheme
3939
Timeout time.Duration
40+
GzipCM bool
4041

4142
overrides *clientcmd.ConfigOverrides
4243
}
@@ -62,6 +63,7 @@ func (c *Configuration) BindFlags(fs *pflag.FlagSet) {
6263
"This value does not override the operator's service account")
6364
fs.DurationVar(&c.Timeout, "timeout", 2*time.Minute,
6465
"Duration to wait for the command to complete before failing")
66+
fs.BoolVar(&c.GzipCM, "gzip-configmap", false, "If 'true', the configmap data will be compressed")
6567
}
6668

6769
func (c *Configuration) Load() error {
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
package fbcindex
2+
3+
import (
4+
"bytes"
5+
"compress/gzip"
6+
"fmt"
7+
"strings"
8+
"text/template"
9+
10+
corev1 "k8s.io/api/core/v1"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
)
13+
14+
const (
15+
yamlSeparator = "\n---\n"
16+
gzipSuffixLength = 13
17+
maxGZIPLength = maxConfigMapSize - gzipSuffixLength
18+
19+
ConfigMapEncodingAnnotationKey = "olm.contentEncoding"
20+
ConfigMapEncodingAnnotationGzip = "gzip+base64"
21+
)
22+
23+
type configMapWriter interface {
24+
newConfigMap() *corev1.ConfigMap
25+
writeYaml([]string) ([]*corev1.ConfigMap, error)
26+
getFilePath() string
27+
getCommandTemplate() *template.Template
28+
}
29+
30+
type defaultCMWriter struct {
31+
cmName string
32+
namespace string
33+
template *template.Template
34+
}
35+
36+
func newDefaultWriter(name, namespace string) *defaultCMWriter {
37+
t := template.Must(template.New("cmd").Parse(fbcGzipCmdTemplate))
38+
return &defaultCMWriter{
39+
cmName: name,
40+
namespace: namespace,
41+
template: t,
42+
}
43+
}
44+
45+
// container entrypoint command for FBC type images.
46+
const fbcCmdTemplate = `opm serve {{ .FBCIndexRootDir}} -p {{ .GRPCPort }}`
47+
48+
func (cmw defaultCMWriter) newConfigMap() *corev1.ConfigMap {
49+
return &corev1.ConfigMap{
50+
TypeMeta: metav1.TypeMeta{
51+
APIVersion: corev1.SchemeGroupVersion.String(),
52+
Kind: "ConfigMap",
53+
},
54+
ObjectMeta: metav1.ObjectMeta{
55+
Namespace: cmw.namespace,
56+
},
57+
Data: map[string]string{},
58+
}
59+
}
60+
61+
func (cmw defaultCMWriter) writeYaml(yamlDefs []string) ([]*corev1.ConfigMap, error) {
62+
cm := cmw.newConfigMap()
63+
cm.SetName(fmt.Sprintf("%s-partition-1", cmw.cmName))
64+
configMaps := []*corev1.ConfigMap{cm}
65+
66+
partitionCount := 1
67+
68+
// for each chunk of yaml see if it can be added to the ConfigMap partition
69+
for _, yamlDef := range yamlDefs {
70+
yamlDef = strings.TrimSpace(yamlDef)
71+
if len(strings.TrimSpace(yamlDef)) == 0 {
72+
continue
73+
}
74+
// If the ConfigMap has data then lets attempt to add to it
75+
if len(cm.Data) != 0 {
76+
// Create a copy to use to verify that adding the data doesn't
77+
// exceed the max ConfigMap size of 1 MiB.
78+
tempCm := cm.DeepCopy()
79+
tempCm.Data[defaultConfigMapKey] = tempCm.Data[defaultConfigMapKey] + yamlSeparator + yamlDef
80+
81+
// if it would be too large adding the data then partition it.
82+
if tempCm.Size() >= maxConfigMapSize {
83+
// Create a new ConfigMap
84+
cm = cmw.newConfigMap()
85+
86+
// Set the ConfigMap name based on the partition it is
87+
cm.SetName(fmt.Sprintf("%s-partition-%d", cmw.cmName, partitionCount+1))
88+
89+
// Since adding this data would have made the previous
90+
// ConfigMap too large, add it to this new one.
91+
// No chunk of YAML from the bundle should cause
92+
// the ConfigMap size to exceed 1 MiB and if
93+
// somehow it does then there is a problem with the
94+
// YAML itself. We can't reasonably break it up smaller
95+
// since it is a single object.
96+
cm.Data[defaultConfigMapKey] = yamlDef
97+
98+
// Add the ConfigMap to the list of ConfigMaps
99+
configMaps = append(configMaps, cm)
100+
101+
} else {
102+
// if adding the data to the ConfigMap
103+
// doesn't make the ConfigMap exceed the
104+
// size limit then actually add it.
105+
cm.Data = tempCm.Data
106+
}
107+
} else {
108+
// If there is no data in the ConfigMap
109+
// then this is the first pass. Since it is
110+
// the first pass go ahead and add the data.
111+
cm.Data[defaultConfigMapKey] = yamlDef
112+
}
113+
}
114+
115+
return configMaps, nil
116+
}
117+
118+
func (cmw defaultCMWriter) getFilePath() string {
119+
return fmt.Sprintf("%s.yaml", defaultConfigMapKey)
120+
}
121+
122+
func (cmw defaultCMWriter) getCommandTemplate() *template.Template {
123+
return cmw.template
124+
}
125+
126+
type gzipCMWriter struct {
127+
actualBuff *bytes.Buffer
128+
helperBuff *bytes.Buffer
129+
actualWriter *gzip.Writer
130+
helperWriter *gzip.Writer
131+
cmName string
132+
namespace string
133+
template *template.Template
134+
}
135+
136+
func newGZIPWriter(name, namespace string) *gzipCMWriter {
137+
actualBuff := &bytes.Buffer{}
138+
helperBuff := &bytes.Buffer{}
139+
t := template.Must(template.New("cmd").Parse(fbcGzipCmdTemplate))
140+
141+
return &gzipCMWriter{
142+
actualBuff: actualBuff,
143+
helperBuff: helperBuff,
144+
actualWriter: gzip.NewWriter(actualBuff),
145+
helperWriter: gzip.NewWriter(helperBuff),
146+
cmName: name,
147+
namespace: namespace,
148+
template: t,
149+
}
150+
}
151+
152+
// container entrypoint command for gzipped FBC type images.
153+
const fbcGzipCmdTemplate = `` +
154+
`serverDir=/var/tmp/{{ .FBCIndexRootDir }}/;` +
155+
`mkdir ${serverDir};` +
156+
`for dir in {{ .FBCIndexRootDir }}/*configmap-partition-*;` +
157+
`do targetDir="/var/tmp/${dir}";` +
158+
`mkdir ${targetDir};` +
159+
`for f in ${dir}/*gz; ` +
160+
`do cat $f | gzip -d -c > "/var/tmp/${f%.*}";` +
161+
`done;` +
162+
`done;` +
163+
`opm serve ${serverDir} -p {{ .GRPCPort }}`
164+
165+
func (cmw *gzipCMWriter) reset() {
166+
cmw.actualBuff.Reset()
167+
cmw.actualWriter.Reset(cmw.actualBuff)
168+
cmw.helperBuff.Reset()
169+
cmw.helperWriter.Reset(cmw.helperBuff)
170+
}
171+
172+
func (cmw *gzipCMWriter) newConfigMap() *corev1.ConfigMap {
173+
return &corev1.ConfigMap{
174+
TypeMeta: metav1.TypeMeta{
175+
APIVersion: corev1.SchemeGroupVersion.String(),
176+
Kind: "ConfigMap",
177+
},
178+
ObjectMeta: metav1.ObjectMeta{
179+
Namespace: cmw.namespace,
180+
Annotations: map[string]string{
181+
ConfigMapEncodingAnnotationKey: ConfigMapEncodingAnnotationGzip,
182+
},
183+
},
184+
BinaryData: map[string][]byte{},
185+
}
186+
}
187+
188+
func (cmw *gzipCMWriter) writeYaml(yamlDefs []string) ([]*corev1.ConfigMap, error) {
189+
defer cmw.reset()
190+
191+
cm := cmw.newConfigMap()
192+
cm.SetName(fmt.Sprintf("%s-partition-1", cmw.cmName))
193+
configMaps := []*corev1.ConfigMap{cm}
194+
195+
partitionCount := 1
196+
197+
// for each chunk of yaml see if it can be added to the ConfigMap partition
198+
for _, yamlDef := range yamlDefs {
199+
yamlDef = strings.TrimSpace(yamlDef)
200+
if len(yamlDef) == 0 {
201+
continue
202+
}
203+
204+
if cmw.actualBuff.Len() > 0 {
205+
data := []byte(yamlSeparator + yamlDef)
206+
_, err := cmw.helperWriter.Write(data)
207+
if err != nil {
208+
return nil, err
209+
}
210+
211+
err = cmw.helperWriter.Flush()
212+
if err != nil {
213+
return nil, err
214+
}
215+
216+
if cm.Size()+cmw.helperBuff.Len() > maxGZIPLength {
217+
err = cmw.actualWriter.Close()
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
err = cmw.actualWriter.Flush()
223+
if err != nil {
224+
return nil, err
225+
}
226+
227+
cm.BinaryData[defaultConfigMapKey] = make([]byte, cmw.actualBuff.Len())
228+
copy(cm.BinaryData[defaultConfigMapKey], cmw.actualBuff.Bytes())
229+
230+
cmw.reset()
231+
232+
partitionCount++
233+
cm = cmw.newConfigMap()
234+
cm.SetName(fmt.Sprintf("%s-partition-%d", cmw.cmName, partitionCount))
235+
configMaps = append(configMaps, cm)
236+
237+
data = []byte(yamlDef)
238+
_, err = cmw.helperWriter.Write(data)
239+
if err != nil {
240+
return nil, err
241+
}
242+
_, err = cmw.actualWriter.Write(data)
243+
if err != nil {
244+
return nil, err
245+
}
246+
} else {
247+
_, err = cmw.actualWriter.Write(data)
248+
if err != nil {
249+
return nil, err
250+
}
251+
}
252+
} else {
253+
data := []byte(yamlDef)
254+
_, err := cmw.helperWriter.Write(data)
255+
if err != nil {
256+
return nil, err
257+
}
258+
_, err = cmw.actualWriter.Write(data)
259+
if err != nil {
260+
return nil, err
261+
}
262+
}
263+
}
264+
265+
// write the data of the last cm
266+
err := cmw.actualWriter.Close()
267+
if err != nil {
268+
return nil, err
269+
}
270+
271+
cm.BinaryData[defaultConfigMapKey] = cmw.actualBuff.Bytes()
272+
273+
return configMaps, nil
274+
}
275+
276+
func (cmw *gzipCMWriter) getFilePath() string {
277+
return fmt.Sprintf("%s.yaml.gz", defaultConfigMapKey)
278+
}
279+
280+
func (cmw *gzipCMWriter) getCommandTemplate() *template.Template {
281+
return cmw.template
282+
}

0 commit comments

Comments
 (0)