Skip to content
Merged
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
16 changes: 11 additions & 5 deletions api/v1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ const (
TypeInstalled = "Installed"
TypeProgressing = "Progressing"

// Installed reasons
ReasonAbsent = "Absent"

// Progressing reasons
ReasonSucceeded = "Succeeded"
ReasonRetrying = "Retrying"
ReasonBlocked = "Blocked"
ReasonRolloutInProgress = "RolloutInProgress"
ReasonRetrying = "Retrying"
ReasonBlocked = "Blocked"

// Terminal reasons
// Deprecation reasons
ReasonDeprecated = "Deprecated"
ReasonFailed = "Failed"

// Common reasons
ReasonSucceeded = "Succeeded"
ReasonFailed = "Failed"
)
288 changes: 155 additions & 133 deletions cmd/operator-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ import (
crcache "sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/client"
crcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"

Expand Down Expand Up @@ -317,38 +319,6 @@ func run() error {
return err
}

coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create core client")
return err
}
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
}

cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
ext := obj.(*ocv1.ClusterExtension)
return ext.Spec.Namespace, nil
}),
helmclient.ClientRestConfigMapper(clientRestConfigMapper),
)
if err != nil {
setupLog.Error(err, "unable to config for creating helm client")
return err
}

acg, err := action.NewWrappedActionClientGetter(cfgGetter,
helmclient.WithFailureRollbacks(false),
)
if err != nil {
setupLog.Error(err, "unable to create helm client")
return err
}

certPoolWatcher, err := httputil.NewCertPoolWatcher(cfg.catalogdCasDir, ctrl.Log.WithName("cert-pool"))
if err != nil {
setupLog.Error(err, "unable to create CA certificate pool")
Expand Down Expand Up @@ -434,118 +404,32 @@ func run() error {
crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()),
}

// determine if PreAuthorizer should be enabled based on feature gate
var preAuth authorization.PreAuthorizer
if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) {
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient())
}

// create applier
var ctrlBuilderOpts []controllers.ControllerBuilderOption
var extApplier controllers.Applier
certProvider := getCertificateProvider()
if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
// TODO: add support for preflight checks
// TODO: better scheme handling - which types do we want to support?
_ = apiextensionsv1.AddToScheme(mgr.GetScheme())
extApplier = &applier.Boxcutter{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
RevisionGenerator: &applier.SimpleRevisionGenerator{
Scheme: mgr.GetScheme(),
BundleRenderer: &applier.RegistryV1BundleRenderer{
BundleRenderer: registryv1.Renderer,
CertificateProvider: certProvider,
},
},
Preflights: preflights,
}
ctrlBuilderOpts = append(ctrlBuilderOpts, controllers.WithOwns(&ocv1.ClusterExtensionRevision{}))
} else {
// now initialize the helmApplier, assigning the potentially nil preAuth
extApplier = &applier.Helm{
ActionClientGetter: acg,
Preflights: preflights,
BundleToHelmChartConverter: &convert.BundleToHelmChartConverter{
BundleRenderer: registryv1.Renderer,
CertificateProvider: certProvider,
IsWebhookSupportEnabled: certProvider != nil,
},
PreAuthorizer: preAuth,
HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{},
}
}

cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
ext := obj.(*ocv1.ClusterExtension)
err := cm.Delete(ext)
return crfinalizer.Result{}, err
}))
if err != nil {
setupLog.Error(err, "unable to register content manager cleanup finalizer")
return err
ceReconciler := &controllers.ClusterExtensionReconciler{
Client: cl,
Resolver: resolver,
ImageCache: imageCache,
ImagePuller: imagePuller,
Finalizers: clusterExtensionFinalizers,
}

if err = (&controllers.ClusterExtensionReconciler{
Client: cl,
Resolver: resolver,
ImageCache: imageCache,
ImagePuller: imagePuller,
Applier: extApplier,
InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg},
Finalizers: clusterExtensionFinalizers,
Manager: cm,
}).SetupWithManager(mgr, ctrlBuilderOpts...); err != nil {
ceController, err := ceReconciler.SetupWithManager(mgr, ctrlBuilderOpts...)
if err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
return err
}

if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
// Boxcutter
const (
boxcutterSystemPrefixFieldOwner = "olm.operatorframework.io"
)

discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
setupLog.Error(err, "unable to create discovery client")
return err
}

trackingCache, err := managedcache.NewTrackingCache(
ctrl.Log.WithName("trackingCache"),
restConfig,
crcache.Options{
Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(),
},
)
if err != nil {
setupLog.Error(err, "unable to create boxcutter tracking cache")
}
if err := mgr.Add(trackingCache); err != nil {
setupLog.Error(err, "unable to set up tracking cache")
}

if err = (&controllers.ClusterExtensionRevisionReconciler{
Client: cl,
RevisionEngine: machinery.NewRevisionEngine(
machinery.NewPhaseEngine(
machinery.NewObjectEngine(
mgr.GetScheme(), trackingCache, mgr.GetClient(),
ownerhandling.NewNative(mgr.GetScheme()),
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), boxcutterSystemPrefixFieldOwner),
boxcutterSystemPrefixFieldOwner, boxcutterSystemPrefixFieldOwner,
),
validation.NewClusterPhaseValidator(mgr.GetRESTMapper(), mgr.GetClient()),
),
validation.NewRevisionValidator(), mgr.GetClient(),
),
TrackingCache: trackingCache,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtensionRevision")
return err
}
err = setupBoxcutter(mgr, ceReconciler, preflights)
} else {
err = setupHelm(mgr, ceReconciler, preflights, ceController, clusterExtensionFinalizers)
}
if err != nil {
setupLog.Error(err, "unable to setup lifecycler")
return err
}

if err = (&controllers.ClusterCatalogReconciler{
Expand Down Expand Up @@ -602,6 +486,144 @@ func getCertificateProvider() render.CertificateProvider {
return nil
}

func setupBoxcutter(mgr manager.Manager, ceReconciler *controllers.ClusterExtensionReconciler, preflights []applier.Preflight) error {
certProvider := getCertificateProvider()

// TODO: add support for preflight checks
// TODO: better scheme handling - which types do we want to support?
_ = apiextensionsv1.AddToScheme(mgr.GetScheme())
ceReconciler.Applier = &applier.Boxcutter{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
RevisionGenerator: &applier.SimpleRevisionGenerator{
Scheme: mgr.GetScheme(),
BundleRenderer: &applier.RegistryV1BundleRenderer{
BundleRenderer: registryv1.Renderer,
CertificateProvider: certProvider,
},
},
Preflights: preflights,
}
ceReconciler.RevisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}

// Boxcutter
const (
boxcutterSystemPrefixFieldOwner = "olm.operatorframework.io"
)

discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
if err != nil {
return fmt.Errorf("unable to create discovery client: %w", err)
}

trackingCache, err := managedcache.NewTrackingCache(
ctrl.Log.WithName("trackingCache"),
mgr.GetConfig(),
crcache.Options{
Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(),
},
)
if err != nil {
return fmt.Errorf("unable to create boxcutter tracking cache: %v", err)
}
if err := mgr.Add(trackingCache); err != nil {
return fmt.Errorf("unable to add tracking cache to manager: %v", err)
}

if err = (&controllers.ClusterExtensionRevisionReconciler{
Client: mgr.GetClient(),
RevisionEngine: machinery.NewRevisionEngine(
machinery.NewPhaseEngine(
machinery.NewObjectEngine(
mgr.GetScheme(), trackingCache, mgr.GetClient(),
ownerhandling.NewNative(mgr.GetScheme()),
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), boxcutterSystemPrefixFieldOwner),
boxcutterSystemPrefixFieldOwner, boxcutterSystemPrefixFieldOwner,
),
validation.NewClusterPhaseValidator(mgr.GetRESTMapper(), mgr.GetClient()),
),
validation.NewRevisionValidator(), mgr.GetClient(),
),
TrackingCache: trackingCache,
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to setup ClusterExtensionRevision controller: %w", err)
}
return nil
}

func setupHelm(
mgr manager.Manager,
ceReconciler *controllers.ClusterExtensionReconciler,
preflights []applier.Preflight,
ceController crcontroller.Controller,
clusterExtensionFinalizers crfinalizer.Registerer,
) error {
coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
if err != nil {
return fmt.Errorf("unable to create core client: %w", err)
}
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
}

cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
ext := obj.(*ocv1.ClusterExtension)
return ext.Spec.Namespace, nil
}),
helmclient.ClientRestConfigMapper(clientRestConfigMapper),
)
if err != nil {
return fmt.Errorf("unable to create helm action config getter: %w", err)
}

acg, err := action.NewWrappedActionClientGetter(cfgGetter,
helmclient.WithFailureRollbacks(false),
)
if err != nil {
return fmt.Errorf("unable to create helm action client getter: %w", err)
}

// determine if PreAuthorizer should be enabled based on feature gate
var preAuth authorization.PreAuthorizer
if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) {
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient())
}

cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
ext := obj.(*ocv1.ClusterExtension)
err := cm.Delete(ext)
return crfinalizer.Result{}, err
}))
if err != nil {
setupLog.Error(err, "unable to register content manager cleanup finalizer")
return err
}

certProvider := getCertificateProvider()

// now initialize the helmApplier, assigning the potentially nil preAuth
ceReconciler.Applier = &applier.Helm{
ActionClientGetter: acg,
Preflights: preflights,
BundleToHelmChartConverter: &convert.BundleToHelmChartConverter{
BundleRenderer: registryv1.Renderer,
CertificateProvider: certProvider,
IsWebhookSupportEnabled: certProvider != nil,
},
HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{},
PreAuthorizer: preAuth,
Watcher: ceController,
Manager: cm,
}
ceReconciler.RevisionStatesGetter = &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg}
return nil
}

func main() {
if err := operatorControllerCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ require (
k8s.io/klog/v2 v2.130.1
k8s.io/kubernetes v1.33.2
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
pkg.package-operator.run/boxcutter v0.4.0
pkg.package-operator.run/boxcutter v0.5.1
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/controller-tools v0.18.0
sigs.k8s.io/crdify v0.4.1-0.20250613143457-398e4483fb58
Expand Down Expand Up @@ -97,7 +97,7 @@ require (
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
Expand All @@ -111,7 +111,7 @@ require (
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gobuffalo/flect v1.0.3 // indirect
Expand Down Expand Up @@ -230,8 +230,8 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
google.golang.org/protobuf v1.36.7 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading
Loading