Skip to content

Commit

Permalink
All observeFuncs make take into account existing OCI Digest in snapshots
Browse files Browse the repository at this point in the history
Signed-off-by: Soule BA <[email protected]>
  • Loading branch information
souleb committed Apr 15, 2024
1 parent 48fa9eb commit 94ccc4f
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 5 deletions.
6 changes: 6 additions & 0 deletions internal/reconcile/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ func mutateOCIDigest(obj *v2.HelmRelease, obs release.Observation) release.Obser
return obs
}

func releaseToObservation(rls *helmrelease.Release, snapshot *v2.Snapshot) release.Observation {
obs := release.ObserveRelease(rls)
obs.OCIDigest = snapshot.OCIDigest
return obs
}

// observeRelease returns a storage.ObserveFunc that stores the observed
// releases in the given observedReleases map.
// It can be used for Helm actions that modify multiple releases in the
Expand Down
2 changes: 1 addition & 1 deletion internal/reconcile/rollback_remediation.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func observeRollback(obj *v2.HelmRelease) storage.ObserveFunc {
for i := range obj.Status.History {
snap := obj.Status.History[i]
if snap.Targets(rls.Name, rls.Namespace, rls.Version) {
newSnap := release.ObservedToSnapshot(release.ObserveRelease(rls))
newSnap := release.ObservedToSnapshot(releaseToObservation(rls, snap))
newSnap.SetTestHooks(snap.GetTestHooks())
obj.Status.History[i] = newSnap
return
Expand Down
43 changes: 43 additions & 0 deletions internal/reconcile/rollback_remediation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,4 +613,47 @@ func Test_observeRollback(t *testing.T) {
expect,
}))
})

t.Run("rollback with update to previous deployed with OCI Digest", func(t *testing.T) {
g := NewWithT(t)

previous := &v2.Snapshot{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 2,
Status: helmrelease.StatusFailed.String(),
OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
}
latest := &v2.Snapshot{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 3,
Status: helmrelease.StatusDeployed.String(),
OCIDigest: "sha256:aedc2b0de1576a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
}

obj := &v2.HelmRelease{
Status: v2.HelmReleaseStatus{
History: v2.Snapshots{
latest,
previous,
},
},
}
rls := helmrelease.Mock(&helmrelease.MockReleaseOptions{
Name: previous.Name,
Namespace: previous.Namespace,
Version: previous.Version,
Status: helmrelease.StatusSuperseded,
})
obs := release.ObserveRelease(rls)
obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6"
expect := release.ObservedToSnapshot(obs)

observeRollback(obj)(rls)
g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{
latest,
expect,
}))
})
}
3 changes: 2 additions & 1 deletion internal/reconcile/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ func observeTest(obj *v2.HelmRelease) storage.ObserveFunc {
}

// Update the latest snapshot with the test result.
tested := release.ObservedToSnapshot(release.ObserveRelease(rls))
latest := obj.Status.History.Latest()
tested := release.ObservedToSnapshot(releaseToObservation(rls, latest))
tested.SetTestHooks(release.TestHooksFromRelease(rls))
obj.Status.History[0] = tested
}
Expand Down
32 changes: 32 additions & 0 deletions internal/reconcile/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,38 @@ func Test_observeTest(t *testing.T) {
}))
})

t.Run("test with current OCI Digest", func(t *testing.T) {
g := NewWithT(t)

obj := &v2.HelmRelease{
Status: v2.HelmReleaseStatus{
History: v2.Snapshots{
&v2.Snapshot{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 1,
OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
},
},
},
}
rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 1,
}, testutil.ReleaseWithHooks(testHookFixtures))

obs := release.ObserveRelease(rls)
obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6"
expect := release.ObservedToSnapshot(obs)
expect.SetTestHooks(release.TestHooksFromRelease(rls))

observeTest(obj)(rls)
g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{
expect,
}))
})

t.Run("test targeting different version than latest", func(t *testing.T) {
g := NewWithT(t)

Expand Down
2 changes: 1 addition & 1 deletion internal/reconcile/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func observeUninstall(obj *v2.HelmRelease) storage.ObserveFunc {
for i := range obj.Status.History {
snap := obj.Status.History[i]
if snap.Targets(rls.Name, rls.Namespace, rls.Version) {
newSnap := release.ObservedToSnapshot(release.ObserveRelease(rls))
newSnap := release.ObservedToSnapshot(releaseToObservation(rls, snap))
newSnap.SetTestHooks(snap.GetTestHooks())
obj.Status.History[i] = newSnap
return
Expand Down
32 changes: 32 additions & 0 deletions internal/reconcile/uninstall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,4 +702,36 @@ func Test_observeUninstall(t *testing.T) {
current,
}))
})
t.Run("uninstall of current with OCI Digest", func(t *testing.T) {
g := NewWithT(t)

current := &v2.Snapshot{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 1,
Status: helmrelease.StatusDeployed.String(),
OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
}
obj := &v2.HelmRelease{
Status: v2.HelmReleaseStatus{
History: v2.Snapshots{
current,
},
},
}
rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{
Name: current.Name,
Namespace: current.Namespace,
Version: current.Version,
Status: helmrelease.StatusUninstalled,
})
obs := release.ObserveRelease(rls)
obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6"
expect := release.ObservedToSnapshot(obs)

observeUninstall(obj)(rls)
g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{
expect,
}))
})
}
18 changes: 16 additions & 2 deletions internal/reconcile/unlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (r *Unlock) Reconcile(_ context.Context, req *Request) error {
}

// Ensure the release is in a pending state.
cur := release.ObservedToSnapshot(release.ObserveRelease(rls))
cur := processCurrentSnaphot(req.Object, rls)
if status := rls.Info.Status; status.IsPending() {
// Update pending status to failed and persist.
rls.SetStatus(helmrelease.StatusFailed, fmt.Sprintf("Release unlocked from stale '%s' state", status.String()))
Expand Down Expand Up @@ -154,9 +154,23 @@ func observeUnlock(obj *v2.HelmRelease) storage.ObserveFunc {
for i := range obj.Status.History {
snap := obj.Status.History[i]
if snap.Targets(rls.Name, rls.Namespace, rls.Version) {
obj.Status.History[i] = release.ObservedToSnapshot(release.ObserveRelease(rls))
obj.Status.History[i] = release.ObservedToSnapshot(releaseToObservation(rls, snap))
return
}
}
}
}

// processCurrentSnaphot processes the current snapshot based on a Helm release.
// It also looks for the OCIDigest in the corresponding v2.HelmRelease history and
// updates the current snapshot with the OCIDigest if found.
func processCurrentSnaphot(obj *v2.HelmRelease, rls *helmrelease.Release) *v2.Snapshot {
cur := release.ObservedToSnapshot(release.ObserveRelease(rls))
for i := range obj.Status.History {
snap := obj.Status.History[i]
if snap.Targets(rls.Name, rls.Namespace, rls.Version) {
cur.OCIDigest = snap.OCIDigest
}
}
return cur
}
121 changes: 121 additions & 0 deletions internal/reconcile/unlock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,95 @@ func TestUnlock_success(t *testing.T) {
}))
}

func TestUnlock_withOCIDigest(t *testing.T) {
g := NewWithT(t)

namedNS, err := testEnv.CreateNamespace(context.TODO(), mockReleaseNamespace)
g.Expect(err).NotTo(HaveOccurred())
t.Cleanup(func() {
_ = testEnv.Delete(context.TODO(), namedNS)
})
releaseNamespace := namedNS.Name

rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{
Name: mockReleaseName,
Namespace: releaseNamespace,
Chart: testutil.BuildChart(),
Version: 4,
Status: helmrelease.StatusPendingInstall,
})

obs := release.ObserveRelease(rls)
obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6"
snap := release.ObservedToSnapshot(obs)

obj := &v2.HelmRelease{
Spec: v2.HelmReleaseSpec{
ReleaseName: mockReleaseName,
TargetNamespace: releaseNamespace,
StorageNamespace: releaseNamespace,
Timeout: &metav1.Duration{Duration: 100 * time.Millisecond},
},
Status: v2.HelmReleaseStatus{
History: v2.Snapshots{
snap,
},
},
}

getter, err := RESTClientGetterFromManager(testEnv.Manager, obj.GetReleaseNamespace())
g.Expect(err).ToNot(HaveOccurred())

cfg, err := action.NewConfigFactory(getter,
action.WithStorage(action.DefaultStorageDriver, obj.GetStorageNamespace()),
)
g.Expect(err).ToNot(HaveOccurred())

store := helmstorage.Init(cfg.Driver)
g.Expect(store.Create(rls)).To(Succeed())

recorder := testutil.NewFakeRecorder(10, false)
got := NewUnlock(cfg, recorder).Reconcile(context.TODO(), &Request{
Object: obj,
})

g.Expect(got).ToNot(HaveOccurred())

g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(
[]metav1.Condition{
*conditions.FalseCondition(meta.ReadyCondition, "PendingRelease", "Unlocked Helm release"),
*conditions.FalseCondition(v2.ReleasedCondition, "PendingRelease", "Unlocked Helm release"),
}))

releases, _ := store.History(mockReleaseName)
helmreleaseutil.SortByRevision(releases)
expected := release.ObserveRelease(releases[0])
expected.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6"
g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{
release.ObservedToSnapshot(expected),
}))

expectMsg := fmt.Sprintf(fmtUnlockSuccess,
fmt.Sprintf("%s/%s.v%d", rls.Namespace, snap.Name, snap.Version),
fmt.Sprintf("%s@%s", rls.Chart.Name(), rls.Chart.Metadata.Version),
rls.Info.Status.String())

g.Expect(recorder.GetEvents()).To(ConsistOf([]corev1.Event{
{
Type: corev1.EventTypeNormal,
Reason: "PendingRelease",
Message: expectMsg,
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
eventMetaGroupKey(metaOCIDigestKey): expected.OCIDigest,
eventMetaGroupKey(eventv1.MetaRevisionKey): rls.Chart.Metadata.Version,
eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, rls.Config).String(),
},
},
},
}))
}

func Test_observeUnlock(t *testing.T) {
t.Run("unlock", func(t *testing.T) {
g := NewWithT(t)
Expand Down Expand Up @@ -487,6 +576,38 @@ func Test_observeUnlock(t *testing.T) {
}))
})

t.Run("unlock with OCI Digest", func(t *testing.T) {
g := NewWithT(t)

obj := &v2.HelmRelease{
Status: v2.HelmReleaseStatus{
History: v2.Snapshots{
{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 1,
Status: helmrelease.StatusPendingRollback.String(),
OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
},
},
},
}
rls := helmrelease.Mock(&helmrelease.MockReleaseOptions{
Name: mockReleaseName,
Namespace: mockReleaseNamespace,
Version: 1,
Status: helmrelease.StatusFailed,
})
obs := release.ObserveRelease(rls)
obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6"
expect := release.ObservedToSnapshot(obs)
observeUnlock(obj)(rls)

g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{
expect,
}))
})

t.Run("unlock without current", func(t *testing.T) {
g := NewWithT(t)

Expand Down

0 comments on commit 94ccc4f

Please sign in to comment.