Skip to content

Commit

Permalink
controller: adopt release based on v2beta1 state
Browse files Browse the repository at this point in the history
This allows the controller to be updated from `v2beta1` to `v2beta2`
without triggering a release to settle state.

It does this by looking at the previous successful release as recorded
for the `v2beta1` object, and if found, recording a snapshot for it in
the new `History` field of the status.

This feature can be disabled by setting the `AdoptLegacyReleases`
feature flag to `false`.

Signed-off-by: Hidde Beydals <[email protected]>
  • Loading branch information
hiddeco committed Nov 22, 2023
1 parent 04350dd commit c9ddc0f
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 5 deletions.
5 changes: 5 additions & 0 deletions api/v2beta2/helmrelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,11 @@ type HelmReleaseStatus struct {
// +optional
LastAttemptedValuesChecksum string `json:"lastAttemptedValuesChecksum,omitempty"`

// LastReleaseRevision is the revision of the last successful Helm release.
// Deprecated: Use History instead.
// +optional
LastReleaseRevision int `json:"lastReleaseRevision,omitempty"`

// LastAttemptedConfigDigest is the digest for the config (better known as
// "values") of the last reconciliation attempt.
// +optional
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2049,6 +2049,10 @@ spec:
reconcile request value, so a change of the annotation value can
be detected.
type: string
lastReleaseRevision:
description: 'LastReleaseRevision is the revision of the last successful
Helm release. Deprecated: Use History instead.'
type: integer
observedGeneration:
description: ObservedGeneration is the last observed generation.
format: int64
Expand Down
13 changes: 13 additions & 0 deletions docs/api/v2beta2/helm.md
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,19 @@ Deprecated: Use LastAttemptedConfigDigest instead.</p>
</tr>
<tr>
<td>
<code>lastReleaseRevision</code><br>
<em>
int
</em>
</td>
<td>
<em>(Optional)</em>
<p>LastReleaseRevision is the revision of the last successful Helm release.
Deprecated: Use History instead.</p>
</td>
</tr>
<tr>
<td>
<code>lastAttemptedConfigDigest</code><br>
<em>
string
Expand Down
64 changes: 64 additions & 0 deletions internal/controller/helmrelease_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ import (
"github.com/fluxcd/helm-controller/internal/action"
"github.com/fluxcd/helm-controller/internal/chartutil"
"github.com/fluxcd/helm-controller/internal/digest"
"github.com/fluxcd/helm-controller/internal/features"
"github.com/fluxcd/helm-controller/internal/kube"
"github.com/fluxcd/helm-controller/internal/loader"
intpredicates "github.com/fluxcd/helm-controller/internal/predicates"
intreconcile "github.com/fluxcd/helm-controller/internal/reconcile"
"github.com/fluxcd/helm-controller/internal/release"
)

// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -288,6 +290,16 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
return ctrl.Result{}, err
}

// Attempt to adopt "legacy" v2beta1 release state on a best-effort basis.
// If this fails, the controller will fall back to performing an upgrade
// to settle on the desired state.
// TODO(hidde): remove this in a future release.
if ok, _ := features.Enabled(features.AdoptLegacyReleases); ok {
if err := r.adoptLegacyRelease(ctx, getter, obj); err != nil {
log.Error(err, "failed to adopt v2beta1 release state")
}
}

// If the release target configuration has changed, we need to uninstall the
// previous release target first. If we did not do this, the installation would
// fail due to resources already existing.
Expand Down Expand Up @@ -315,6 +327,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
obj.Status.LastAttemptedRevision = loadedChart.Metadata.Version
obj.Status.LastAttemptedConfigDigest = chartutil.DigestValues(digest.Canonical, values).String()
obj.Status.LastAttemptedValuesChecksum = ""
obj.Status.LastReleaseRevision = 0

// Construct config factory for any further Helm actions.
cfg, err := action.NewConfigFactory(getter,
Expand Down Expand Up @@ -508,6 +521,57 @@ func (r *HelmReleaseReconciler) checkDependencies(ctx context.Context, obj *v2.H
return nil
}

// adoptLegacyRelease attempts to adopt a v2beta1 release into a v2beta2
// release.
// This is done by retrieving the last successful release from the Helm storage
// and converting it to a v2beta2 release snapshot.
// If the v2beta1 release has already been adopted, this function is a no-op.
func (r *HelmReleaseReconciler) adoptLegacyRelease(ctx context.Context, getter genericclioptions.RESTClientGetter, obj *v2.HelmRelease) error {
if obj.Status.LastReleaseRevision < 1 || len(obj.Status.History) > 0 {
return nil
}

var (
log = ctrl.LoggerFrom(ctx).V(logger.DebugLevel)
storageNamespace = obj.GetStorageNamespace()
releaseNamespace = obj.GetReleaseNamespace()
releaseName = obj.GetReleaseName()
version = obj.Status.LastReleaseRevision
)

log.Info("adopting %s/%s.v%d release from v2beta1 state", releaseNamespace, releaseName, version)

// Construct config factory for current release.
cfg, err := action.NewConfigFactory(getter,
action.WithStorage(action.DefaultStorageDriver, storageNamespace),
action.WithStorageLog(action.NewDebugLog(ctrl.LoggerFrom(ctx).V(logger.TraceLevel))),
)

// Get the last successful release based on the observation for the v2beta1
// object.
rls, err := cfg.NewStorage().Get(releaseName, version)
if err != nil {
return err
}

// Convert it to a v2beta2 release snapshot.
snap := release.ObservedToSnapshot(release.ObserveRelease(rls))

// If tests are enabled, include them as well.
if obj.GetTest().Enable {
snap.SetTestHooks(release.TestHooksFromRelease(rls))
}

// Adopt it as the current release in the history.
obj.Status.History = append(obj.Status.History, snap)
obj.Status.StorageNamespace = storageNamespace

// Erase the last release revision from the status.
obj.Status.LastReleaseRevision = 0

return nil
}

func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, obj *v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
opts := []kube.Option{
kube.WithNamespace(obj.GetReleaseNamespace()),
Expand Down
Loading

0 comments on commit c9ddc0f

Please sign in to comment.