Skip to content

Commit afd611a

Browse files
committed
refactor Applier interface and improve status reporting
Signed-off-by: Joe Lanford <[email protected]>
1 parent 7bfb7c7 commit afd611a

File tree

6 files changed

+184
-171
lines changed

6 files changed

+184
-171
lines changed

api/v1/common_types.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@ const (
2020
TypeInstalled = "Installed"
2121
TypeProgressing = "Progressing"
2222

23+
// Installed reasons
24+
ReasonAbsent = "Absent"
25+
2326
// Progressing reasons
24-
ReasonSucceeded = "Succeeded"
25-
ReasonRetrying = "Retrying"
26-
ReasonBlocked = "Blocked"
27+
ReasonRolloutInProgress = "RolloutInProgress"
28+
ReasonRetrying = "Retrying"
29+
ReasonBlocked = "Blocked"
2730

28-
// Terminal reasons
31+
// Deprecation reasons
2932
ReasonDeprecated = "Deprecated"
30-
ReasonFailed = "Failed"
33+
34+
// Common reasons
35+
ReasonSucceeded = "Succeeded"
36+
ReasonFailed = "Failed"
3137
)

cmd/operator-controller/main.go

Lines changed: 82 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -317,38 +317,6 @@ func run() error {
317317
return err
318318
}
319319

320-
coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
321-
if err != nil {
322-
setupLog.Error(err, "unable to create core client")
323-
return err
324-
}
325-
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
326-
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
327-
if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
328-
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
329-
}
330-
331-
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
332-
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
333-
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
334-
ext := obj.(*ocv1.ClusterExtension)
335-
return ext.Spec.Namespace, nil
336-
}),
337-
helmclient.ClientRestConfigMapper(clientRestConfigMapper),
338-
)
339-
if err != nil {
340-
setupLog.Error(err, "unable to config for creating helm client")
341-
return err
342-
}
343-
344-
acg, err := action.NewWrappedActionClientGetter(cfgGetter,
345-
helmclient.WithFailureRollbacks(false),
346-
)
347-
if err != nil {
348-
setupLog.Error(err, "unable to create helm client")
349-
return err
350-
}
351-
352320
certPoolWatcher, err := httputil.NewCertPoolWatcher(cfg.catalogdCasDir, ctrl.Log.WithName("cert-pool"))
353321
if err != nil {
354322
setupLog.Error(err, "unable to create CA certificate pool")
@@ -434,24 +402,30 @@ func run() error {
434402
crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()),
435403
}
436404

437-
// determine if PreAuthorizer should be enabled based on feature gate
438-
var preAuth authorization.PreAuthorizer
439-
if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) {
440-
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient())
405+
var ctrlBuilderOpts []controllers.ControllerBuilderOption
406+
if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
407+
ctrlBuilderOpts = append(ctrlBuilderOpts, controllers.WithOwns(&ocv1.ClusterExtensionRevision{}))
408+
}
409+
410+
ceReconciler := &controllers.ClusterExtensionReconciler{
411+
Client: cl,
412+
Resolver: resolver,
413+
ImageCache: imageCache,
414+
ImagePuller: imagePuller,
415+
Finalizers: clusterExtensionFinalizers,
416+
}
417+
ceController, err := ceReconciler.SetupWithManager(mgr, ctrlBuilderOpts...)
418+
if err != nil {
419+
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
420+
return err
441421
}
442422

443-
// create applier
444-
var (
445-
ctrlBuilderOpts []controllers.ControllerBuilderOption
446-
extApplier controllers.Applier
447-
revisionStatesGetter controllers.RevisionStatesGetter
448-
)
449423
certProvider := getCertificateProvider()
450424
if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
451425
// TODO: add support for preflight checks
452426
// TODO: better scheme handling - which types do we want to support?
453427
_ = apiextensionsv1.AddToScheme(mgr.GetScheme())
454-
extApplier = &applier.Boxcutter{
428+
ceReconciler.Applier = &applier.Boxcutter{
455429
Client: mgr.GetClient(),
456430
Scheme: mgr.GetScheme(),
457431
RevisionGenerator: &applier.SimpleRevisionGenerator{
@@ -463,50 +437,8 @@ func run() error {
463437
},
464438
Preflights: preflights,
465439
}
466-
revisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}
467-
ctrlBuilderOpts = append(ctrlBuilderOpts, controllers.WithOwns(&ocv1.ClusterExtensionRevision{}))
468-
} else {
469-
// now initialize the helmApplier, assigning the potentially nil preAuth
470-
extApplier = &applier.Helm{
471-
ActionClientGetter: acg,
472-
Preflights: preflights,
473-
BundleToHelmChartConverter: &convert.BundleToHelmChartConverter{
474-
BundleRenderer: registryv1.Renderer,
475-
CertificateProvider: certProvider,
476-
IsWebhookSupportEnabled: certProvider != nil,
477-
},
478-
PreAuthorizer: preAuth,
479-
HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{},
480-
}
481-
revisionStatesGetter = &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg}
482-
}
483-
484-
cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
485-
err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
486-
ext := obj.(*ocv1.ClusterExtension)
487-
err := cm.Delete(ext)
488-
return crfinalizer.Result{}, err
489-
}))
490-
if err != nil {
491-
setupLog.Error(err, "unable to register content manager cleanup finalizer")
492-
return err
493-
}
494-
495-
if err = (&controllers.ClusterExtensionReconciler{
496-
Client: cl,
497-
Resolver: resolver,
498-
ImageCache: imageCache,
499-
ImagePuller: imagePuller,
500-
Applier: extApplier,
501-
RevisionStatesGetter: revisionStatesGetter,
502-
Finalizers: clusterExtensionFinalizers,
503-
Manager: cm,
504-
}).SetupWithManager(mgr, ctrlBuilderOpts...); err != nil {
505-
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
506-
return err
507-
}
440+
ceReconciler.RevisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}
508441

509-
if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
510442
// Boxcutter
511443
const (
512444
boxcutterSystemPrefixFieldOwner = "olm.operatorframework.io"
@@ -550,6 +482,70 @@ func run() error {
550482
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtensionRevision")
551483
return err
552484
}
485+
} else {
486+
coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
487+
if err != nil {
488+
setupLog.Error(err, "unable to create core client")
489+
return err
490+
}
491+
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
492+
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
493+
if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
494+
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
495+
}
496+
497+
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
498+
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
499+
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
500+
ext := obj.(*ocv1.ClusterExtension)
501+
return ext.Spec.Namespace, nil
502+
}),
503+
helmclient.ClientRestConfigMapper(clientRestConfigMapper),
504+
)
505+
if err != nil {
506+
setupLog.Error(err, "unable to config for creating helm client")
507+
return err
508+
}
509+
510+
acg, err := action.NewWrappedActionClientGetter(cfgGetter,
511+
helmclient.WithFailureRollbacks(false),
512+
)
513+
if err != nil {
514+
setupLog.Error(err, "unable to create helm client")
515+
return err
516+
}
517+
518+
// determine if PreAuthorizer should be enabled based on feature gate
519+
var preAuth authorization.PreAuthorizer
520+
if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) {
521+
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient())
522+
}
523+
524+
cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
525+
err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
526+
ext := obj.(*ocv1.ClusterExtension)
527+
err := cm.Delete(ext)
528+
return crfinalizer.Result{}, err
529+
}))
530+
if err != nil {
531+
setupLog.Error(err, "unable to register content manager cleanup finalizer")
532+
return err
533+
}
534+
// now initialize the helmApplier, assigning the potentially nil preAuth
535+
ceReconciler.Applier = &applier.Helm{
536+
ActionClientGetter: acg,
537+
Preflights: preflights,
538+
BundleToHelmChartConverter: &convert.BundleToHelmChartConverter{
539+
BundleRenderer: registryv1.Renderer,
540+
CertificateProvider: certProvider,
541+
IsWebhookSupportEnabled: certProvider != nil,
542+
},
543+
HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{},
544+
PreAuthorizer: preAuth,
545+
Watcher: ceController,
546+
Manager: cm,
547+
}
548+
ceReconciler.RevisionStatesGetter = &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg}
553549
}
554550

555551
if err = (&controllers.ClusterCatalogReconciler{

internal/operator-controller/applier/boxcutter.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"slices"
1313

1414
"github.com/davecgh/go-spew/spew"
15+
"k8s.io/apimachinery/pkg/api/meta"
1516
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1617
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1718
"k8s.io/apimachinery/pkg/runtime"
@@ -104,9 +105,8 @@ type Boxcutter struct {
104105
Preflights []Preflight
105106
}
106107

107-
func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) ([]client.Object, string, error) {
108-
objs, err := bc.apply(ctx, contentFS, ext, objectLabels, revisionAnnotations)
109-
return objs, "", err
108+
func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) {
109+
return bc.apply(ctx, contentFS, ext, objectLabels, revisionAnnotations)
110110
}
111111

112112
func (bc *Boxcutter) getObjects(rev *ocv1.ClusterExtensionRevision) []client.Object {
@@ -119,17 +119,17 @@ func (bc *Boxcutter) getObjects(rev *ocv1.ClusterExtensionRevision) []client.Obj
119119
return objs
120120
}
121121

122-
func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) ([]client.Object, error) {
122+
func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) {
123123
// Generate desired revision
124124
desiredRevision, err := bc.RevisionGenerator.GenerateRevision(contentFS, ext, objectLabels, revisionAnnotations)
125125
if err != nil {
126-
return nil, err
126+
return false, "", err
127127
}
128128

129129
// List all existing revisions
130130
existingRevisions, err := bc.getExistingRevisions(ctx, ext.GetName())
131131
if err != nil {
132-
return nil, err
132+
return false, "", err
133133
}
134134
desiredHash := computeSHA256Hash(desiredRevision.Spec.Phases)
135135

@@ -159,12 +159,16 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
159159
case StateNeedsInstall:
160160
err := preflight.Install(ctx, plainObjs)
161161
if err != nil {
162-
return nil, err
162+
return false, "", err
163163
}
164+
// TODO: jlanford's IDE says that "StateNeedsUpgrade" condition is always true, but
165+
// it isn't immediately obvious why that is. Perhaps len(existingRevisions) is
166+
// always greater than 0 (seems unlikely), or shouldSkipPreflight always returns
167+
// true (and we continue) when state == StateNeedsInstall?
164168
case StateNeedsUpgrade:
165169
err := preflight.Upgrade(ctx, plainObjs)
166170
if err != nil {
167-
return nil, err
171+
return false, "", err
168172
}
169173
}
170174
}
@@ -189,19 +193,24 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
189193
}
190194

191195
if err := controllerutil.SetControllerReference(ext, newRevision, bc.Scheme); err != nil {
192-
return nil, fmt.Errorf("set ownerref: %w", err)
196+
return false, "", fmt.Errorf("set ownerref: %w", err)
193197
}
194198
if err := bc.Client.Create(ctx, newRevision); err != nil {
195-
return nil, fmt.Errorf("creating new Revision: %w", err)
199+
return false, "", fmt.Errorf("creating new Revision: %w", err)
196200
}
201+
currentRevision = newRevision
197202
}
198203

199204
// TODO: Delete archived previous revisions over a certain revision limit
200205

201-
// TODO: Read status from revision.
202-
203-
// Collect objects
204-
return bc.getObjects(desiredRevision), nil
206+
// TODO: Define constants for the ClusterExtensionRevision condition types.
207+
installedCondition := meta.FindStatusCondition(currentRevision.Status.Conditions, "Succeeded")
208+
if installedCondition == nil {
209+
return false, "New revision created", nil
210+
} else if installedCondition.Status != metav1.ConditionTrue {
211+
return false, installedCondition.Message, nil
212+
}
213+
return true, "", nil
205214
}
206215

207216
// getExistingRevisions returns the list of ClusterExtensionRevisions for a ClusterExtension with name extName in revision order (oldest to newest)

0 commit comments

Comments
 (0)