Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions cmd/operator-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,22 +407,31 @@ func run() error {
return httputil.BuildHTTPClient(cpwCatalogd)
})

resolver := &resolve.CatalogResolver{
WalkCatalogsFunc: resolve.CatalogWalker(
func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) {
var catalogs ocv1.ClusterCatalogList
if err := cl.List(ctx, &catalogs, option...); err != nil {
return nil, err
}
return catalogs.Items, nil
resolver := &resolve.MultiResolver{
CatalogResolver: resolve.CatalogResolver{
WalkCatalogsFunc: resolve.CatalogWalker(
func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) {
var catalogs ocv1.ClusterCatalogList
if err := cl.List(ctx, &catalogs, option...); err != nil {
return nil, err
}
return catalogs.Items, nil
},
catalogClient.GetPackage,
),
Validations: []resolve.ValidationFunc{
resolve.NoDependencyValidation,
},
catalogClient.GetPackage,
),
Validations: []resolve.ValidationFunc{
resolve.NoDependencyValidation,
},
}

if features.OperatorControllerFeatureGate.Enabled(features.DirectBundleInstall) {
resolver.BundleResolver = &resolve.BundleImageRefResolver{
ImagePuller: imagePuller,
ImageCache: imageCache,
}
}

aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create apiextensions client")
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ require (
github.com/gobuffalo/flect v1.0.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-migrate/migrate/v4 v4.19.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
Expand Down
8 changes: 8 additions & 0 deletions internal/operator-controller/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
WebhookProviderOpenshiftServiceCA featuregate.Feature = "WebhookProviderOpenshiftServiceCA"
HelmChartSupport featuregate.Feature = "HelmChartSupport"
BoxcutterRuntime featuregate.Feature = "BoxcutterRuntime"
DirectBundleInstall featuregate.Feature = "DirectBundleInstall"
)

var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
Expand Down Expand Up @@ -80,6 +81,13 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature
PreRelease: featuregate.Alpha,
LockToDefault: false,
},

// DirectBundleInstall allows for direct bundle installation via annotation
DirectBundleInstall: {
Default: false,
PreRelease: featuregate.Alpha,
LockToDefault: false,
},
}

var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
Expand Down
84 changes: 84 additions & 0 deletions internal/operator-controller/resolve/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package resolve

import (
"context"
"fmt"
"io/fs"
"reflect"

bsemver "github.com/blang/semver/v4"

"github.com/operator-framework/operator-registry/alpha/action"
"github.com/operator-framework/operator-registry/alpha/declcfg"

ocv1 "github.com/operator-framework/operator-controller/api/v1"
"github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil"
"github.com/operator-framework/operator-controller/internal/shared/util/image"
)

const (
directBundleInstallImageAnnotation = "alpha.olm.operatorframework.io/bundle-image-ref"
)

type BundleImageRefResolver struct {
ImagePuller image.Puller
ImageCache image.Cache
}

func (r *BundleImageRefResolver) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
if ext.Annotations == nil || ext.Annotations[directBundleInstallImageAnnotation] == "" {
return nil, nil, nil, fmt.Errorf("ClusterExtension is missing required annotation %s", directBundleInstallImageAnnotation)
}
bundleFS, canonicalRef, _, err := r.ImagePuller.Pull(ctx, ext.Name, ext.Annotations[directBundleInstallImageAnnotation], r.ImageCache)
if err != nil {
return nil, nil, nil, err
}

// TODO: This is a temporary workaround to get the bundle from the filesystem
// until the operator-registry library is updated to support reading from a
// fs.FS. This will be removed once the library is updated.
bundlePath, err := getDirFSPath(bundleFS)
if err != nil {
return nil, nil, nil, fmt.Errorf("expected to be able to recover bundle path from bundleFS: %v", err)
}

// Render the bundle
render := action.Render{
Refs: []string{bundlePath},
AllowedRefMask: action.RefBundleDir,
}
fbc, err := render.Run(ctx)
if err != nil {
return nil, nil, nil, err
}
if len(fbc.Bundles) != 1 {
return nil, nil, nil, fmt.Errorf("expected exactly one bundle but found %d", len(fbc.Bundles))
}
bundle := fbc.Bundles[0]
bundle.Image = canonicalRef.String()
v, err := bundleutil.GetVersion(bundle)
if err != nil {
return nil, nil, nil, fmt.Errorf("error determining bundle version: %w", err)
}
return &bundle, v, nil, nil
}

// A function to recover the underlying path string from os.DirFS
func getDirFSPath(f fs.FS) (string, error) {
v := reflect.ValueOf(f)

// Check if the underlying type is a string (its kind)
if v.Kind() != reflect.String {
return "", fmt.Errorf("underlying type is not a string, it is %s", v.Kind())
}

// The type itself (os.dirFS) is unexported, but its Kind is a string.
// We can convert the reflect.Value back to a regular string using .Interface()
// after converting it to a basic string type.
path, ok := v.Convert(reflect.TypeOf("")).Interface().(string)
if !ok {
return "", fmt.Errorf("could not convert reflected value to string")
}

return path, nil
}
14 changes: 14 additions & 0 deletions internal/operator-controller/resolve/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,17 @@ type Func func(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle
func (f Func) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
return f(ctx, ext, installedBundle)
}

// MultiResolver uses the CatalogResolver by default. It will use the currently internal,feature gated, and annotation-powered BundleImageRefResolver
// if it is non-nil and the necessary annotation is present
type MultiResolver struct {
CatalogResolver CatalogResolver
BundleResolver *BundleImageRefResolver
}

func (m MultiResolver) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
if m.BundleResolver != nil && ext.Annotations != nil && ext.Annotations[directBundleInstallImageAnnotation] != "" {
return m.BundleResolver.Resolve(ctx, ext, installedBundle)
}
return m.CatalogResolver.Resolve(ctx, ext, installedBundle)
}
Loading