Skip to content

Commit 6f34900

Browse files
author
Per Goncalves da Silva
committed
Add internal direct bundle install method
Signed-off-by: Per Goncalves da Silva <pegoncal@redhat.com> Signed-off-by: Per G. da Silva <pegoncal@redhat.com>
1 parent 926d57e commit 6f34900

File tree

5 files changed

+128
-12
lines changed

5 files changed

+128
-12
lines changed

cmd/operator-controller/main.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -407,22 +407,31 @@ func run() error {
407407
return httputil.BuildHTTPClient(cpwCatalogd)
408408
})
409409

410-
resolver := &resolve.CatalogResolver{
411-
WalkCatalogsFunc: resolve.CatalogWalker(
412-
func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) {
413-
var catalogs ocv1.ClusterCatalogList
414-
if err := cl.List(ctx, &catalogs, option...); err != nil {
415-
return nil, err
416-
}
417-
return catalogs.Items, nil
410+
resolver := &resolve.MultiResolver{
411+
CatalogResolver: resolve.CatalogResolver{
412+
WalkCatalogsFunc: resolve.CatalogWalker(
413+
func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) {
414+
var catalogs ocv1.ClusterCatalogList
415+
if err := cl.List(ctx, &catalogs, option...); err != nil {
416+
return nil, err
417+
}
418+
return catalogs.Items, nil
419+
},
420+
catalogClient.GetPackage,
421+
),
422+
Validations: []resolve.ValidationFunc{
423+
resolve.NoDependencyValidation,
418424
},
419-
catalogClient.GetPackage,
420-
),
421-
Validations: []resolve.ValidationFunc{
422-
resolve.NoDependencyValidation,
423425
},
424426
}
425427

428+
if features.OperatorControllerFeatureGate.Enabled(features.DirectBundleInstall) {
429+
resolver.BundleResolver = &resolve.BundleImageRefResolver{
430+
ImagePuller: imagePuller,
431+
ImageCache: imageCache,
432+
}
433+
}
434+
426435
aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig())
427436
if err != nil {
428437
setupLog.Error(err, "unable to create apiextensions client")

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ require (
126126
github.com/gobuffalo/flect v1.0.3 // indirect
127127
github.com/gobwas/glob v0.2.3 // indirect
128128
github.com/gogo/protobuf v1.3.2 // indirect
129+
github.com/golang-migrate/migrate/v4 v4.19.0 // indirect
129130
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
130131
github.com/golang/protobuf v1.5.4 // indirect
131132
github.com/google/btree v1.1.3 // indirect

internal/operator-controller/features/features.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
WebhookProviderOpenshiftServiceCA featuregate.Feature = "WebhookProviderOpenshiftServiceCA"
1919
HelmChartSupport featuregate.Feature = "HelmChartSupport"
2020
BoxcutterRuntime featuregate.Feature = "BoxcutterRuntime"
21+
DirectBundleInstall featuregate.Feature = "DirectBundleInstall"
2122
)
2223

2324
var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
@@ -80,6 +81,13 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature
8081
PreRelease: featuregate.Alpha,
8182
LockToDefault: false,
8283
},
84+
85+
// DirectBundleInstall allows for direct bundle installation via annotation
86+
DirectBundleInstall: {
87+
Default: false,
88+
PreRelease: featuregate.Alpha,
89+
LockToDefault: false,
90+
},
8391
}
8492

8593
var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package resolve
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/fs"
7+
"reflect"
8+
9+
bsemver "github.com/blang/semver/v4"
10+
11+
"github.com/operator-framework/operator-registry/alpha/action"
12+
"github.com/operator-framework/operator-registry/alpha/declcfg"
13+
14+
ocv1 "github.com/operator-framework/operator-controller/api/v1"
15+
"github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil"
16+
"github.com/operator-framework/operator-controller/internal/shared/util/image"
17+
)
18+
19+
const (
20+
directBundleInstallImageAnnotation = "alpha.olm.operatorframework.io/bundle-image-ref"
21+
)
22+
23+
type BundleImageRefResolver struct {
24+
ImagePuller image.Puller
25+
ImageCache image.Cache
26+
}
27+
28+
func (r *BundleImageRefResolver) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
29+
if ext.Annotations == nil || ext.Annotations[directBundleInstallImageAnnotation] == "" {
30+
return nil, nil, nil, fmt.Errorf("ClusterExtension is missing required annotation %s", directBundleInstallImageAnnotation)
31+
}
32+
bundleFS, canonicalRef, _, err := r.ImagePuller.Pull(ctx, ext.Name, ext.Annotations[directBundleInstallImageAnnotation], r.ImageCache)
33+
if err != nil {
34+
return nil, nil, nil, err
35+
}
36+
37+
// TODO: This is a temporary workaround to get the bundle from the filesystem
38+
// until the operator-registry library is updated to support reading from a
39+
// fs.FS. This will be removed once the library is updated.
40+
bundlePath, err := getDirFSPath(bundleFS)
41+
if err != nil {
42+
return nil, nil, nil, fmt.Errorf("expected to be able to recover bundle path from bundleFS: %v", err)
43+
}
44+
45+
// Render the bundle
46+
render := action.Render{
47+
Refs: []string{bundlePath},
48+
AllowedRefMask: action.RefBundleDir,
49+
}
50+
fbc, err := render.Run(ctx)
51+
if err != nil {
52+
return nil, nil, nil, err
53+
}
54+
if len(fbc.Bundles) != 1 {
55+
return nil, nil, nil, fmt.Errorf("expected exactly one bundle but found %d", len(fbc.Bundles))
56+
}
57+
bundle := fbc.Bundles[0]
58+
bundle.Image = canonicalRef.String()
59+
v, err := bundleutil.GetVersion(bundle)
60+
if err != nil {
61+
return nil, nil, nil, fmt.Errorf("error determining bundle version: %w", err)
62+
}
63+
return &bundle, v, nil, nil
64+
}
65+
66+
// A function to recover the underlying path string from os.DirFS
67+
func getDirFSPath(f fs.FS) (string, error) {
68+
v := reflect.ValueOf(f)
69+
70+
// Check if the underlying type is a string (its kind)
71+
if v.Kind() != reflect.String {
72+
return "", fmt.Errorf("underlying type is not a string, it is %s", v.Kind())
73+
}
74+
75+
// The type itself (os.dirFS) is unexported, but its Kind is a string.
76+
// We can convert the reflect.Value back to a regular string using .Interface()
77+
// after converting it to a basic string type.
78+
path, ok := v.Convert(reflect.TypeOf("")).Interface().(string)
79+
if !ok {
80+
return "", fmt.Errorf("could not convert reflected value to string")
81+
}
82+
83+
return path, nil
84+
}

internal/operator-controller/resolve/resolver.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,17 @@ type Func func(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle
1919
func (f Func) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
2020
return f(ctx, ext, installedBundle)
2121
}
22+
23+
// MultiResolver uses the CatalogResolver by default. It will use the currently internal,feature gated, and annotation-powered BundleImageRefResolver
24+
// if it is non-nil and the necessary annotation is present
25+
type MultiResolver struct {
26+
CatalogResolver CatalogResolver
27+
BundleResolver *BundleImageRefResolver
28+
}
29+
30+
func (m MultiResolver) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
31+
if m.BundleResolver != nil && ext.Annotations != nil && ext.Annotations[directBundleInstallImageAnnotation] != "" {
32+
return m.BundleResolver.Resolve(ctx, ext, installedBundle)
33+
}
34+
return m.CatalogResolver.Resolve(ctx, ext, installedBundle)
35+
}

0 commit comments

Comments
 (0)