Skip to content

Commit 5282bfa

Browse files
(bugfix) Enable run bundle command to handle large FBC index (#5868)
* Enable `run bundle` command to handle large FBC index i.e operatorhub catalog image for example * Create ConfigMap and add it as a VolumeMount to the registry pod container PodSpec * Set ConfigMap owner reference as CatalogSource for cleanup purposes * Generate only extra FBC after validating that bundle not in index already and store it in the ConfigMap as a file within FBC root directory Signed-off-by: rashmigottipati <[email protected]>
1 parent 584ada8 commit 5282bfa

File tree

5 files changed

+130
-39
lines changed

5 files changed

+130
-39
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
entries:
2+
- description: >
3+
Enable `run bundle` command to handle large File-Based Catalog index images
4+
by generating the extra FBC with the bundle contents and mounting a ConfigMap with that extra FBC,
5+
without regenerating the entire index.
6+
7+
# kind is one of:
8+
# - addition
9+
# - change
10+
# - deprecation
11+
# - removal
12+
# - bugfix
13+
kind: bugfix
14+
15+
# Is this a breaking change?
16+
breaking: false

internal/olm/fbcutil/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
const (
3434
SchemaChannel = "olm.channel"
3535
SchemaPackage = "olm.package"
36-
DefaultChannel = "operator-sdk-run"
36+
DefaultChannel = "operator-sdk-run-bundle"
3737
)
3838

3939
const (

internal/olm/operator/bundle/install.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ func generateFBCContent(ctx context.Context, f *fbcutil.FBCContext, bundleImage,
166166

167167
if indexImage != fbcutil.DefaultIndexImage { // non-default index image was specified.
168168
// since an index image is specified, the bundle image will be added to the index image.
169-
// addBundleToIndexImage will ensure that the bundle is not already present in the index image and error out if it does.
170-
declcfg, err = addBundleToIndexImage(ctx, indexImage, bundleDeclcfg)
169+
// generateExtraFBC will ensure that the bundle is not already present in the index image and error out if it does.
170+
declcfg, err = generateExtraFBC(ctx, indexImage, bundleDeclcfg)
171171
if err != nil {
172172
return "", fmt.Errorf("error adding bundle image %q to index image %q: %v", bundleImage, indexImage, err)
173173
}
@@ -184,9 +184,10 @@ func generateFBCContent(ctx context.Context, f *fbcutil.FBCContext, bundleImage,
184184
return content, nil
185185
}
186186

187-
// addBundleToIndexImage adds the bundle to an existing index image if the bundle is not already present in the index image.
188-
func addBundleToIndexImage(ctx context.Context, indexImage string, bundleDeclConfig fbcutil.BundleDeclcfg) (*declarativeconfig.DeclarativeConfig, error) {
189-
log.Infof("Rendering a File-Based Catalog of the Index Image %q", indexImage)
187+
// generateExtraFBC verifies that a bundle is not already present on the index and if not, it renders the bundle contents into a
188+
// declarative config type.
189+
func generateExtraFBC(ctx context.Context, indexImage string, bundleDeclConfig fbcutil.BundleDeclcfg) (*declarativeconfig.DeclarativeConfig, error) {
190+
log.Infof("Rendering a File-Based Catalog of the Index Image %q to verify if bundle %q is present", indexImage, bundleDeclConfig.Bundle.Name)
190191
log.SetOutput(ioutil.Discard)
191192
render := action.Render{
192193
Refs: []string{indexImage},
@@ -218,14 +219,16 @@ func addBundleToIndexImage(ctx context.Context, indexImage string, bundleDeclCon
218219
}
219220
}
220221

221-
imageDeclConfig.Bundles = append(imageDeclConfig.Bundles, bundleDeclConfig.Bundle)
222-
imageDeclConfig.Channels = append(imageDeclConfig.Channels, bundleDeclConfig.Channel)
222+
extraDeclConfig := &declarativeconfig.DeclarativeConfig{
223+
Bundles: []declarativeconfig.Bundle{bundleDeclConfig.Bundle},
224+
Channels: []declarativeconfig.Channel{bundleDeclConfig.Channel},
225+
}
223226

224227
if !isPackagePresent {
225-
imageDeclConfig.Packages = append(imageDeclConfig.Packages, bundleDeclConfig.Package)
228+
extraDeclConfig.Packages = []declarativeconfig.Package{bundleDeclConfig.Package}
226229
}
227230

228-
log.Infof("Inserted the new bundle %q into the index image: %s", bundleDeclConfig.Bundle.Name, indexImage)
231+
log.Infof("Generated the extra FBC for the bundle image %q", bundleDeclConfig.Bundle.Name)
229232

230-
return imageDeclConfig, nil
233+
return extraDeclConfig, nil
231234
}

internal/olm/operator/registry/fbcindex/fbc_registry_pod.go

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ import (
2020
"errors"
2121
"fmt"
2222
"path"
23-
"path/filepath"
24-
"strings"
2523
"text/template"
2624
"time"
2725

2826
"github.com/operator-framework/api/pkg/operators/v1alpha1"
2927
log "github.com/sirupsen/logrus"
3028
corev1 "k8s.io/api/core/v1"
29+
apierrors "k8s.io/apimachinery/pkg/api/errors"
3130
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3231
"k8s.io/apimachinery/pkg/types"
3332
"k8s.io/apimachinery/pkg/util/wait"
33+
"k8s.io/client-go/util/retry"
3434
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3535

3636
"github.com/operator-framework/operator-sdk/internal/olm/operator"
@@ -44,6 +44,9 @@ const (
4444

4545
defaultContainerName = "registry-grpc"
4646
defaultContainerPortName = "grpc"
47+
48+
defaultConfigMapName = "operator-sdk-run-bundle-config"
49+
defaultConfigMapKey = "extraFBC"
4750
)
4851

4952
// FBCRegistryPod holds resources necessary for creation of a registry pod in FBC scenarios.
@@ -64,35 +67,32 @@ type FBCRegistryPod struct { //nolint:maligned
6467
// FBCContent represents the contents of the FBC file (string YAML).
6568
FBCContent string
6669

67-
// FBCDir is the name of the FBC directory name where the FBC resides in.
68-
FBCDir string
69-
70-
// FBCFile represents the FBC filename that has all the contents to be served through the registry pod.
71-
FBCFile string
70+
// FBCIndexRootDir is the FBC directory that exists under root of an FBC container image.
71+
// This directory has the File-Based Catalog representation of a catalog index.
72+
FBCIndexRootDir string
7273

7374
cfg *operator.Configuration
7475
}
7576

7677
// init initializes the FBCRegistryPod struct.
77-
func (f *FBCRegistryPod) init(cfg *operator.Configuration) error {
78+
func (f *FBCRegistryPod) init(cfg *operator.Configuration, cs *v1alpha1.CatalogSource) error {
7879
if f.GRPCPort == 0 {
7980
f.GRPCPort = defaultGRPCPort
8081
}
8182

83+
if f.FBCIndexRootDir == "" {
84+
f.FBCIndexRootDir = "/configs"
85+
}
86+
8287
f.cfg = cfg
8388

8489
// validate the FBCRegistryPod struct and ensure required fields are set
8590
if err := f.validate(); err != nil {
8691
return fmt.Errorf("invalid FBC registry pod: %v", err)
8792
}
8893

89-
bundleImage := f.BundleItems[len(f.BundleItems)-1].ImageTag
90-
trimmedbundleImage := strings.Split(bundleImage, ":")[0]
91-
f.FBCDir = fmt.Sprintf("%s-index", filepath.Join("/tmp", strings.Split(trimmedbundleImage, "/")[2]))
92-
f.FBCFile = filepath.Join(f.FBCDir, strings.Split(bundleImage, ":")[1])
93-
9494
// podForBundleRegistry() to make the pod definition
95-
pod, err := f.podForBundleRegistry()
95+
pod, err := f.podForBundleRegistry(cs)
9696
if err != nil {
9797
return fmt.Errorf("error building registry pod definition: %v", err)
9898
}
@@ -105,7 +105,7 @@ func (f *FBCRegistryPod) init(cfg *operator.Configuration) error {
105105
// sets the catalog source as the owner for the pod and verifies that
106106
// the pod is running
107107
func (f *FBCRegistryPod) Create(ctx context.Context, cfg *operator.Configuration, cs *v1alpha1.CatalogSource) (*corev1.Pod, error) {
108-
if err := f.init(cfg); err != nil {
108+
if err := f.init(cfg, cs); err != nil {
109109
return nil, err
110110
}
111111

@@ -184,7 +184,7 @@ func getPodName(bundleImage string) string {
184184

185185
// podForBundleRegistry constructs and returns the registry pod definition
186186
// and throws error when unable to build the pod definition successfully
187-
func (f *FBCRegistryPod) podForBundleRegistry() (*corev1.Pod, error) {
187+
func (f *FBCRegistryPod) podForBundleRegistry(cs *v1alpha1.CatalogSource) (*corev1.Pod, error) {
188188
// rp was already validated so len(f.BundleItems) must be greater than 0.
189189
bundleImage := f.BundleItems[len(f.BundleItems)-1].ImageTag
190190

@@ -194,13 +194,38 @@ func (f *FBCRegistryPod) podForBundleRegistry() (*corev1.Pod, error) {
194194
return nil, err
195195
}
196196

197+
// create ConfigMap if it does not exist,
198+
// if it exists, then update it with new content.
199+
cm, err := f.createConfigMap(cs)
200+
if err != nil {
201+
return nil, fmt.Errorf("configMap error: %w", err)
202+
}
203+
197204
// make the pod definition
198205
f.pod = &corev1.Pod{
199206
ObjectMeta: metav1.ObjectMeta{
200207
Name: getPodName(bundleImage),
201208
Namespace: f.cfg.Namespace,
202209
},
203210
Spec: corev1.PodSpec{
211+
Volumes: []corev1.Volume{
212+
{
213+
Name: k8sutil.TrimDNS1123Label(cm.Name + "-volume"),
214+
VolumeSource: corev1.VolumeSource{
215+
ConfigMap: &corev1.ConfigMapVolumeSource{
216+
Items: []corev1.KeyToPath{
217+
{
218+
Key: defaultConfigMapKey,
219+
Path: path.Join(defaultConfigMapName, fmt.Sprintf("%s.yaml", defaultConfigMapKey)),
220+
},
221+
},
222+
LocalObjectReference: corev1.LocalObjectReference{
223+
Name: cm.Name,
224+
},
225+
},
226+
},
227+
},
228+
},
204229
Containers: []corev1.Container{
205230
{
206231
Name: defaultContainerName,
@@ -213,6 +238,13 @@ func (f *FBCRegistryPod) podForBundleRegistry() (*corev1.Pod, error) {
213238
Ports: []corev1.ContainerPort{
214239
{Name: defaultContainerPortName, ContainerPort: f.GRPCPort},
215240
},
241+
VolumeMounts: []corev1.VolumeMount{
242+
{
243+
Name: k8sutil.TrimDNS1123Label(cm.Name + "-volume"),
244+
MountPath: path.Join(f.FBCIndexRootDir, cm.Name),
245+
SubPath: cm.Name,
246+
},
247+
},
216248
},
217249
},
218250
},
@@ -221,23 +253,63 @@ func (f *FBCRegistryPod) podForBundleRegistry() (*corev1.Pod, error) {
221253
return f.pod, nil
222254
}
223255

224-
const fbcCmdTemplate = `mkdir -p {{ .FBCDir }} && \
225-
echo '{{ .FBCContent }}' >> {{ .FBCFile }} && \
226-
opm serve {{ .FBCDir }} -p {{ .GRPCPort }}
227-
`
256+
// container creation command for FBC type images.
257+
const fbcCmdTemplate = `opm serve {{ .FBCIndexRootDir}} -p {{ .GRPCPort }}`
258+
259+
// createConfigMap creates a ConfigMap if it does not exist and if it does, then update it with new content.
260+
// Also, sets the owner reference by making CatalogSource the owner of ConfigMap object for cleanup purposes.
261+
func (f *FBCRegistryPod) createConfigMap(cs *v1alpha1.CatalogSource) (*corev1.ConfigMap, error) {
262+
// new ConfigMap
263+
cm := &corev1.ConfigMap{
264+
TypeMeta: metav1.TypeMeta{
265+
APIVersion: corev1.SchemeGroupVersion.String(),
266+
Kind: "ConfigMap",
267+
},
268+
ObjectMeta: metav1.ObjectMeta{
269+
Name: defaultConfigMapName,
270+
Namespace: f.cfg.Namespace,
271+
},
272+
Data: map[string]string{
273+
defaultConfigMapKey: f.FBCContent,
274+
},
275+
}
276+
277+
// set owner reference by making catalog source the owner of ConfigMap object
278+
if err := controllerutil.SetOwnerReference(cs, cm, f.cfg.Scheme); err != nil {
279+
return nil, fmt.Errorf("set configmap %q owner reference: %v", cm.GetName(), err)
280+
}
281+
282+
cmKey := types.NamespacedName{
283+
Namespace: f.cfg.Namespace,
284+
Name: defaultConfigMapName,
285+
}
286+
287+
// create a ConfigMap if it does not exist;
288+
// update it with new data if it already exists.
289+
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
290+
err := f.cfg.Client.Get(context.TODO(), cmKey, cm)
291+
if apierrors.IsNotFound(err) {
292+
if err := f.cfg.Client.Create(context.TODO(), cm); err != nil {
293+
return fmt.Errorf("error creating ConfigMap: %w", err)
294+
}
295+
}
296+
// update ConfigMap with new FBCContent
297+
cm.Data = map[string]string{defaultConfigMapKey: f.FBCContent}
298+
return f.cfg.Client.Update(context.TODO(), cm)
299+
}); err != nil {
300+
return nil, fmt.Errorf("error updating ConfigMap: %w", err)
301+
}
302+
303+
return cm, nil
304+
305+
}
228306

229307
// getContainerCmd uses templating to construct the container command
230308
// and throws error if unable to parse and execute the container command
231309
func (f *FBCRegistryPod) getContainerCmd() (string, error) {
232-
var t *template.Template
233-
// create a custom dirname template function
234-
funcMap := template.FuncMap{
235-
"dirname": path.Dir,
236-
}
237-
238310
// add the custom dirname template function to the
239311
// template's FuncMap and parse the cmdTemplate
240-
t = template.Must(template.New("cmd").Funcs(funcMap).Parse(fbcCmdTemplate))
312+
t := template.Must(template.New("cmd").Parse(fbcCmdTemplate))
241313

242314
// execute the command by applying the parsed template to command
243315
// and write command output to out

internal/olm/operator/registry/index_image.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ func (c IndexImageCatalogCreator) UpdateCatalog(ctx context.Context, cs *v1alpha
412412

413413
err = c.runFBCUpgrade(ctx)
414414
if err != nil {
415-
return fmt.Errorf("unable to determine if index image adopts File-Based Catalog or SQLite format: %v", err)
415+
return fmt.Errorf("error in upgrading FBC: %v", err)
416416
}
417417
}
418418
}

0 commit comments

Comments
 (0)