Skip to content

Commit 03cb1e1

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 03cb1e1

File tree

8 files changed

+683
-179
lines changed

8 files changed

+683
-179
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# entries is a list of entries to include in
2+
# release notes and/or the migration guide
3+
entries:
4+
- description: >
5+
Allow compress the bundle content. Added a new cli flag `--gzip-configmap=true` to the `operator-sdk run bundle`.
6+
This will create compressed configmaps. Use it when getting this error:
7+
`... ConfigMap ... is invalid: []: Too long: must have at most 1048576 bytes`.
8+
Fixes issue [#6323](https://github.com/operator-framework/operator-sdk/issues/6323)
9+
10+
# kind is one of:
11+
# - addition
12+
# - change
13+
# - deprecation
14+
# - removal
15+
# - bugfix
16+
kind: "bugfix"
17+
18+
# Is this a breaking change?
19+
breaking: false

internal/olm/operator/config.go

Lines changed: 3 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,8 @@ 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", false, `If 'true', the configmap data will be compressed. `+
67+
`Useful when getting errors with a message similar to "... ConfigMap ... is invalid: []: Too long: must have at most 1048576 bytes"`)
6568
}
6669

6770
func (c *Configuration) Load() error {
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// Copyright 2023 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fbcindex
16+
17+
import (
18+
"bytes"
19+
"compress/gzip"
20+
"fmt"
21+
"strings"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
)
26+
27+
const (
28+
yamlSeparator = "\n---\n"
29+
gzipSuffixLength = 13
30+
maxGZIPLength = maxConfigMapSize - gzipSuffixLength
31+
32+
ConfigMapEncodingAnnotationKey = "olm.contentEncoding"
33+
ConfigMapEncodingAnnotationGzip = "gzip+base64"
34+
)
35+
36+
/*
37+
This file implements the actual building of the CM list. It uses the template method design pattern to implement both
38+
regular string VM, and compressed binary CM.
39+
40+
The method itself is FBCRegistryPod.getConfigMaps. This file contains the actual implementation of the writing actions,
41+
used by the method.
42+
*/
43+
44+
type configMapWriter interface {
45+
reset()
46+
newConfigMap(string) *corev1.ConfigMap
47+
getFilePath() string
48+
isEmpty() bool
49+
exceedMaxLength(cmSize int, data string) (bool, error)
50+
closeCM(cm *corev1.ConfigMap) error
51+
addData(data string) error
52+
continueAddData(data string) error
53+
writeLastFragment(cm *corev1.ConfigMap) error
54+
}
55+
56+
type defaultCMWriter struct {
57+
cmName string
58+
namespace string
59+
dataBuilder strings.Builder
60+
}
61+
62+
func newDefaultWriter(name, namespace string) *defaultCMWriter {
63+
return &defaultCMWriter{
64+
cmName: name,
65+
namespace: namespace,
66+
dataBuilder: strings.Builder{},
67+
}
68+
}
69+
70+
func (cmw *defaultCMWriter) reset() {
71+
cmw.dataBuilder = strings.Builder{}
72+
}
73+
74+
func (cmw *defaultCMWriter) newConfigMap(name string) *corev1.ConfigMap {
75+
return &corev1.ConfigMap{
76+
TypeMeta: metav1.TypeMeta{
77+
APIVersion: corev1.SchemeGroupVersion.String(),
78+
Kind: "ConfigMap",
79+
},
80+
ObjectMeta: metav1.ObjectMeta{
81+
Name: name,
82+
Namespace: cmw.namespace,
83+
},
84+
Data: map[string]string{},
85+
}
86+
}
87+
88+
func (cmw *defaultCMWriter) isEmpty() bool {
89+
return cmw.dataBuilder.Len() > 0
90+
}
91+
92+
func (cmw *defaultCMWriter) exceedMaxLength(cmSize int, data string) (bool, error) {
93+
return cmSize+cmw.dataBuilder.Len()+len(data) > maxConfigMapSize, nil
94+
}
95+
96+
func (cmw *defaultCMWriter) closeCM(cm *corev1.ConfigMap) error {
97+
cm.Data[defaultConfigMapKey] = cmw.dataBuilder.String()
98+
cmw.dataBuilder = strings.Builder{}
99+
return nil
100+
}
101+
102+
func (cmw *defaultCMWriter) getFilePath() string {
103+
return fmt.Sprintf("%s.yaml", defaultConfigMapKey)
104+
}
105+
106+
func (cmw *defaultCMWriter) addData(data string) error {
107+
cmw.dataBuilder.WriteString(data)
108+
return nil
109+
}
110+
111+
// continueAddData completes adding the data after starting adding it in exceedMaxLength
112+
func (cmw *defaultCMWriter) continueAddData(data string) error {
113+
return cmw.addData(data)
114+
}
115+
116+
func (cmw *defaultCMWriter) writeLastFragment(cm *corev1.ConfigMap) error {
117+
cm.Data[defaultConfigMapKey] = cmw.dataBuilder.String()
118+
119+
return nil
120+
}
121+
122+
type gzipCMWriter struct {
123+
actualBuff *bytes.Buffer
124+
helperBuff *bytes.Buffer
125+
actualWriter *gzip.Writer
126+
helperWriter *gzip.Writer
127+
cmName string
128+
namespace string
129+
}
130+
131+
func newGZIPWriter(name, namespace string) *gzipCMWriter {
132+
actualBuff := &bytes.Buffer{}
133+
helperBuff := &bytes.Buffer{}
134+
135+
return &gzipCMWriter{
136+
actualBuff: actualBuff,
137+
helperBuff: helperBuff,
138+
actualWriter: gzip.NewWriter(actualBuff),
139+
helperWriter: gzip.NewWriter(helperBuff),
140+
cmName: name,
141+
namespace: namespace,
142+
}
143+
}
144+
145+
func (cmw *gzipCMWriter) reset() {
146+
cmw.actualBuff.Reset()
147+
cmw.actualWriter.Reset(cmw.actualBuff)
148+
cmw.helperBuff.Reset()
149+
cmw.helperWriter.Reset(cmw.helperBuff)
150+
}
151+
152+
func (cmw *gzipCMWriter) newConfigMap(name string) *corev1.ConfigMap {
153+
return &corev1.ConfigMap{
154+
TypeMeta: metav1.TypeMeta{
155+
APIVersion: corev1.SchemeGroupVersion.String(),
156+
Kind: "ConfigMap",
157+
},
158+
ObjectMeta: metav1.ObjectMeta{
159+
Namespace: cmw.namespace,
160+
Name: name,
161+
Annotations: map[string]string{
162+
ConfigMapEncodingAnnotationKey: ConfigMapEncodingAnnotationGzip,
163+
},
164+
},
165+
BinaryData: map[string][]byte{},
166+
}
167+
}
168+
169+
func (cmw *gzipCMWriter) getFilePath() string {
170+
return fmt.Sprintf("%s.yaml.gz", defaultConfigMapKey)
171+
}
172+
173+
func (cmw *gzipCMWriter) isEmpty() bool {
174+
return cmw.actualBuff.Len() > 0
175+
}
176+
177+
func (cmw *gzipCMWriter) exceedMaxLength(cmSize int, data string) (bool, error) {
178+
_, err := cmw.helperWriter.Write([]byte(data))
179+
if err != nil {
180+
return false, err
181+
}
182+
183+
err = cmw.helperWriter.Flush()
184+
if err != nil {
185+
return false, err
186+
}
187+
188+
return cmSize+cmw.helperBuff.Len() > maxGZIPLength, nil
189+
}
190+
191+
func (cmw *gzipCMWriter) closeCM(cm *corev1.ConfigMap) error {
192+
err := cmw.actualWriter.Close()
193+
if err != nil {
194+
return err
195+
}
196+
197+
err = cmw.actualWriter.Flush()
198+
if err != nil {
199+
return err
200+
}
201+
202+
cm.BinaryData[defaultConfigMapKey] = make([]byte, cmw.actualBuff.Len())
203+
copy(cm.BinaryData[defaultConfigMapKey], cmw.actualBuff.Bytes())
204+
205+
cmw.reset()
206+
207+
return nil
208+
}
209+
210+
func (cmw *gzipCMWriter) addData(data string) error {
211+
dataBytes := []byte(data)
212+
_, err := cmw.helperWriter.Write(dataBytes)
213+
if err != nil {
214+
return err
215+
}
216+
_, err = cmw.actualWriter.Write(dataBytes)
217+
if err != nil {
218+
return err
219+
}
220+
return nil
221+
}
222+
223+
// continueAddData completes adding the data after starting adding it in exceedMaxLength
224+
func (cmw *gzipCMWriter) continueAddData(data string) error {
225+
_, err := cmw.actualWriter.Write([]byte(data))
226+
if err != nil {
227+
return err
228+
}
229+
return nil
230+
}
231+
232+
func (cmw *gzipCMWriter) writeLastFragment(cm *corev1.ConfigMap) error {
233+
err := cmw.actualWriter.Close()
234+
if err != nil {
235+
return err
236+
}
237+
238+
cm.BinaryData[defaultConfigMapKey] = cmw.actualBuff.Bytes()
239+
return nil
240+
}

0 commit comments

Comments
 (0)