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
4 changes: 2 additions & 2 deletions cmd/operator-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl
controllers.HandleFinalizers(c.finalizers),
controllers.MigrateStorage(storageMigrator),
controllers.RetrieveRevisionStates(revisionStatesGetter),
controllers.ResolveBundle(c.resolver),
controllers.ResolveBundle(c.resolver, c.mgr.GetClient()),
controllers.UnpackBundle(c.imagePuller, c.imageCache),
controllers.ApplyBundleWithBoxcutter(appl),
}
Expand Down Expand Up @@ -737,7 +737,7 @@ func (c *helmReconcilerConfigurator) Configure(ceReconciler *controllers.Cluster
ceReconciler.ReconcileSteps = []controllers.ReconcileStepFunc{
controllers.HandleFinalizers(c.finalizers),
controllers.RetrieveRevisionStates(revisionStatesGetter),
controllers.ResolveBundle(c.resolver),
controllers.ResolveBundle(c.resolver, c.mgr.GetClient()),
controllers.UnpackBundle(c.imagePuller, c.imageCache),
controllers.ApplyBundle(appl),
}
Expand Down
25 changes: 19 additions & 6 deletions internal/operator-controller/applier/boxcutter.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,22 +303,35 @@ func (bc *Boxcutter) createOrUpdate(ctx context.Context, obj client.Object) erro
}

func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) {
// Generate desired revision
desiredRevision, err := bc.RevisionGenerator.GenerateRevision(ctx, contentFS, ext, objectLabels, revisionAnnotations)
// List all existing revisions
existingRevisions, err := bc.getExistingRevisions(ctx, ext.GetName())
if err != nil {
return false, "", err
}

if err := controllerutil.SetControllerReference(ext, desiredRevision, bc.Scheme); err != nil {
return false, "", fmt.Errorf("set ownerref: %w", err)
// If contentFS is nil, we're maintaining the current state without catalog access.
// In this case, we should use the existing installed revision without generating a new one.
if contentFS == nil {
if len(existingRevisions) == 0 {
return false, "", fmt.Errorf("cannot maintain workload: no catalog content available and no previously installed revision found")
}
// Returning true here signals that the rollout has succeeded using the current revision. The
// ClusterExtensionRevision controller will continue to reconcile, apply, and maintain the
// resources defined in that revision via Server-Side Apply, ensuring the workload keeps running
// even when catalog access (and thus new revision content) is unavailable.
return true, "", nil
}

// List all existing revisions
existingRevisions, err := bc.getExistingRevisions(ctx, ext.GetName())
// Generate desired revision
desiredRevision, err := bc.RevisionGenerator.GenerateRevision(ctx, contentFS, ext, objectLabels, revisionAnnotations)
if err != nil {
return false, "", err
}

if err := controllerutil.SetControllerReference(ext, desiredRevision, bc.Scheme); err != nil {
return false, "", fmt.Errorf("set ownerref: %w", err)
}

currentRevision := &ocv1.ClusterExtensionRevision{}
state := StateNeedsInstall
// check if we can update the current revision.
Expand Down
49 changes: 49 additions & 0 deletions internal/operator-controller/applier/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ func (h *Helm) runPreAuthorizationChecks(ctx context.Context, ext *ocv1.ClusterE
}

func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels map[string]string, storageLabels map[string]string) (bool, string, error) {
// If contentFS is nil, we're maintaining the current state without catalog access.
// In this case, reconcile the existing Helm release if it exists.
if contentFS == nil {
ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext)
if err != nil {
return false, "", err
}
return h.reconcileExistingRelease(ctx, ac, ext)
}

chrt, err := h.buildHelmChart(contentFS, ext)
if err != nil {
return false, "", err
Expand Down Expand Up @@ -197,6 +207,45 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
return true, "", nil
}

// reconcileExistingRelease reconciles an existing Helm release without catalog access.
// This is used when the catalog is unavailable but we need to maintain the current installation.
// It reconciles the release and sets up watchers to ensure resources are maintained.
func (h *Helm) reconcileExistingRelease(ctx context.Context, ac helmclient.ActionInterface, ext *ocv1.ClusterExtension) (bool, string, error) {
rel, err := ac.Get(ext.GetName())
if errors.Is(err, driver.ErrReleaseNotFound) {
return false, "", fmt.Errorf("cannot maintain workload: no catalog content available and no previously installed Helm release found")
}
if err != nil {
return false, "", fmt.Errorf("getting current release: %w", err)
}

// Reconcile the existing release to ensure resources are maintained
if err := ac.Reconcile(rel); err != nil {
// Reconcile failed - resources NOT maintained
// Return false (rollout failed) with error
return false, "", err
}

// At this point: Reconcile succeeded - resources ARE maintained
// The operations below are for setting up monitoring (watches).
// If they fail, the resources are still successfully reconciled and maintained,
// so we return true (rollout succeeded) even though monitoring setup failed.
relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name))
if err != nil {
return true, "", err
}
klog.FromContext(ctx).Info("watching managed objects")
cache, err := h.Manager.Get(ctx, ext)
if err != nil {
return true, "", err
}
if err := cache.Watch(ctx, h.Watcher, relObjects...); err != nil {
return true, "", err
}

return true, "", nil
}

func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) {
if h.HelmChartProvider == nil {
return nil, errors.New("HelmChartProvider is nil")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req

// ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension,
// and assigns a specified reason and custom message to any missing condition.
//
//nolint:unparam // reason parameter is designed to be flexible, even if current callers use the same value
func ensureAllConditionsWithReason(ext *ocv1.ClusterExtension, reason v1alpha1.ConditionReason, message string) {
for _, condType := range conditionsets.ConditionTypes {
cond := apimeta.FindStatusCondition(ext.Status.Conditions, condType)
Expand Down
Loading
Loading