diff --git a/api/v2beta2/helmrelease_types.go b/api/v2beta2/helmrelease_types.go
index 01a174653..9db38102f 100644
--- a/api/v2beta2/helmrelease_types.go
+++ b/api/v2beta2/helmrelease_types.go
@@ -1002,11 +1002,16 @@ type HelmReleaseStatus struct {
LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`
// LastAttemptedRevision is the Source revision of the last reconciliation
- // attempt. For OCIRegistry sources, the 12 first characters of the digest are
+ // attempt. For OCIRepository sources, the 12 first characters of the digest are
// appended to the chart version e.g. "1.2.3+1234567890ab".
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
+ // LastAttemptedRevisionDigest is the digest of the last reconciliation attempt.
+ // This is only set for OCIRepository sources.
+ // +optional
+ LastAttemptedRevisionDigest string `json:"lastAttemptedRevisionDigest,omitempty"`
+
// LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last
// reconciliation attempt.
// Deprecated: Use LastAttemptedConfigDigest instead.
diff --git a/api/v2beta2/snapshot_types.go b/api/v2beta2/snapshot_types.go
index 587667665..6dcdb3457 100644
--- a/api/v2beta2/snapshot_types.go
+++ b/api/v2beta2/snapshot_types.go
@@ -156,6 +156,9 @@ type Snapshot struct {
// run by the controller.
// +optional
TestHooks *map[string]*TestHookStatus `json:"testHooks,omitempty"`
+ // OciDigest is the digest of the OCI artifact associated with the release.
+ // +optional
+ OciDigest string `json:"ociDigest,omitempty"`
}
// FullReleaseName returns the full name of the release in the format
diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
index 429c43e3e..2430f0312 100644
--- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
+++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
@@ -1100,6 +1100,10 @@ spec:
description: Namespace is the namespace the release is deployed
to.
type: string
+ ociDigest:
+ description: OciDigest is the digest of the OCI artifact associated
+ with the release.
+ type: string
status:
description: Status is the current state of the release.
type: string
@@ -2374,6 +2378,10 @@ spec:
description: Namespace is the namespace the release is deployed
to.
type: string
+ ociDigest:
+ description: OciDigest is the digest of the OCI artifact associated
+ with the release.
+ type: string
status:
description: Status is the current state of the release.
type: string
@@ -2452,9 +2460,14 @@ spec:
lastAttemptedRevision:
description: |-
LastAttemptedRevision is the Source revision of the last reconciliation
- attempt. For OCIRegistry sources, the 12 first characters of the digest are
+ attempt. For OCIRepository sources, the 12 first characters of the digest are
appended to the chart version e.g. "1.2.3+1234567890ab".
type: string
+ lastAttemptedRevisionDigest:
+ description: |-
+ LastAttemptedRevisionDigest is the digest of the last reconciliation attempt.
+ This is only set for OCIRepository sources.
+ type: string
lastAttemptedValuesChecksum:
description: |-
LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last
diff --git a/docs/api/v2beta2/helm.md b/docs/api/v2beta2/helm.md
index 5decee575..4a00e1f6e 100644
--- a/docs/api/v2beta2/helm.md
+++ b/docs/api/v2beta2/helm.md
@@ -1584,12 +1584,25 @@ string
(Optional)
LastAttemptedRevision is the Source revision of the last reconciliation
-attempt. For OCIRegistry sources, the 12 first characters of the digest are
+attempt. For OCIRepository sources, the 12 first characters of the digest are
appended to the chart version e.g. “1.2.3+1234567890ab”.
|
+lastAttemptedRevisionDigest
+
+string
+
+ |
+
+(Optional)
+ LastAttemptedRevisionDigest is the digest of the last reconciliation attempt.
+This is only set for OCIRepository sources.
+ |
+
+
+
lastAttemptedValuesChecksum
string
@@ -2380,6 +2393,18 @@ TestHookStatus
run by the controller.
|
+
+
+ociDigest
+
+string
+
+ |
+
+(Optional)
+ OciDigest is the digest of the OCI artifact associated with the release.
+ |
+
diff --git a/docs/spec/v2beta2/helmreleases.md b/docs/spec/v2beta2/helmreleases.md
index 159d37054..a8efba435 100644
--- a/docs/spec/v2beta2/helmreleases.md
+++ b/docs/spec/v2beta2/helmreleases.md
@@ -242,26 +242,10 @@ metadata:
namespace: default
spec:
interval: 10m
- timeout: 5m
chartRef:
kind: OCIRepository
name: podinfo
namespace: default
- releaseName: podinfo
- install:
- remediation:
- retries: 3
- upgrade:
- remediation:
- retries: 3
- test:
- enable: true
- driftDetection:
- mode: enabled
- ignore:
- - paths: ["/spec/replicas"]
- target:
- kind: Deployment
values:
replicaCount: 2
```
diff --git a/internal/action/verify.go b/internal/action/verify.go
index 3c6852260..84d1a1978 100644
--- a/internal/action/verify.go
+++ b/internal/action/verify.go
@@ -126,6 +126,11 @@ func VerifyReleaseObject(snapshot *v2.Snapshot, rls *helmrelease.Release) error
verifier := relDig.Verifier()
obs := release.ObserveRelease(rls)
+
+ // unfortunately we have to pass in the OciDigest as is, because helmrelease.Release
+ // does not have a field for it.
+ obs.OciDigest = snapshot.OciDigest
+
if err = obs.Encode(verifier); err != nil {
// We are expected to be able to encode valid JSON, error out without a
// typed error assuming malfunction to signal to e.g. retry.
diff --git a/internal/controller/helmrelease_controller.go b/internal/controller/helmrelease_controller.go
index 0615bb9d4..e3cf00fc9 100644
--- a/internal/controller/helmrelease_controller.go
+++ b/internal/controller/helmrelease_controller.go
@@ -335,7 +335,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
}
- err = mutateChartWithSourceRevision(loadedChart, source)
+ ociDigest, err := mutateChartWithSourceRevision(loadedChart, source)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, "ChartMutateError", err.Error())
return ctrl.Result{}, err
@@ -388,6 +388,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
// Set last attempt values.
obj.Status.LastAttemptedGeneration = obj.Generation
obj.Status.LastAttemptedRevision = loadedChart.Metadata.Version
+ obj.Status.LastAttemptedRevisionDigest = ociDigest
obj.Status.LastAttemptedConfigDigest = chartutil.DigestValues(digest.Canonical, values).String()
obj.Status.LastAttemptedValuesChecksum = ""
obj.Status.LastReleaseRevision = 0
@@ -865,24 +866,28 @@ func getNamespacedName(obj *v2.HelmRelease) (types.NamespacedName, error) {
return namespacedName, nil
}
-func mutateChartWithSourceRevision(chart *chart.Chart, source source.Source) error {
+func mutateChartWithSourceRevision(chart *chart.Chart, source source.Source) (string, error) {
// If the source is an OCIRepository, we can try to mutate the chart version
// with the artifact revision. The revision is either a @ or
// just a digest.
obj, ok := source.(*sourcev1.OCIRepository)
if !ok {
- return nil
+ // if not make sure to return an empty string to delete the digest of the
+ // last attempted revision
+ return "", nil
}
ver, err := semver.NewVersion(chart.Metadata.Version)
if err != nil {
- return err
+ return "", err
}
+
+ var ociDigest string
revision := obj.GetArtifact().Revision
switch {
case strings.Contains(revision, "@"):
tagD := strings.Split(revision, "@")
if len(tagD) != 2 || tagD[0] != chart.Metadata.Version {
- return fmt.Errorf("artifact revision %s does not match chart version %s", tagD[0], chart.Metadata.Version)
+ return "", fmt.Errorf("artifact revision %s does not match chart version %s", tagD[0], chart.Metadata.Version)
}
// algotithm are sha256, sha384, sha512 with the canonical being sha256
// So every digest starts with a sha algorithm and a colon
@@ -890,17 +895,19 @@ func mutateChartWithSourceRevision(chart *chart.Chart, source source.Source) err
// add the digest to the chart version to make sure mutable tags are detected
*ver, err = ver.SetMetadata(sha[0:12])
if err != nil {
- return err
+ return "", err
}
+ ociDigest = sha
default:
// default to the digest
sha := strings.Split(revision, ":")[1]
*ver, err = ver.SetMetadata(sha[0:12])
if err != nil {
- return err
+ return "", err
}
+ ociDigest = sha
}
chart.Metadata.Version = ver.String()
- return nil
+ return ociDigest, nil
}
diff --git a/internal/controller/helmrelease_controller_test.go b/internal/controller/helmrelease_controller_test.go
index f6e4c398a..e54b495fc 100644
--- a/internal/controller/helmrelease_controller_test.go
+++ b/internal/controller/helmrelease_controller_test.go
@@ -186,6 +186,10 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g := NewWithT(t)
chart := &sourcev1b2.HelmChart{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: sourcev1b2.GroupVersion.String(),
+ Kind: sourcev1b2.HelmChartKind,
+ },
ObjectMeta: metav1.ObjectMeta{
Name: "chart",
Namespace: "mock",
@@ -238,6 +242,10 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g := NewWithT(t)
chart := &sourcev1b2.HelmChart{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: sourcev1b2.GroupVersion.String(),
+ Kind: sourcev1b2.HelmChartKind,
+ },
ObjectMeta: metav1.ObjectMeta{
Name: "chart",
Namespace: "mock",
@@ -960,6 +968,10 @@ func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testin
g := NewWithT(t)
ocirepo := &sourcev1b2.OCIRepository{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: sourcev1b2.GroupVersion.String(),
+ Kind: sourcev1b2.OCIRepositoryKind,
+ },
ObjectMeta: metav1.ObjectMeta{
Name: "ocirepo",
Namespace: "mock",
@@ -2972,7 +2984,7 @@ func Test_TryMutateChartWithSourceRevision(t *testing.T) {
},
}
- err := mutateChartWithSourceRevision(c, s)
+ _, err := mutateChartWithSourceRevision(c, s)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
} else {
diff --git a/internal/reconcile/correct_cluster_drift.go b/internal/reconcile/correct_cluster_drift.go
index bbd6f7a31..d46f0172d 100644
--- a/internal/reconcile/correct_cluster_drift.go
+++ b/internal/reconcile/correct_cluster_drift.go
@@ -99,10 +99,10 @@ func (r *CorrectClusterDrift) report(obj *v2.HelmRelease, changeSet *ssa.ChangeS
sb.WriteString(changeSet.String())
}
- r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest), corev1.EventTypeWarning,
+ r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)), corev1.EventTypeWarning,
"DriftCorrectionFailed", sb.String())
case changeSet != nil && len(changeSet.Entries) > 0:
- r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest), corev1.EventTypeNormal,
+ r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)), corev1.EventTypeNormal,
"DriftCorrected", "Cluster state of release %s has been corrected:\n%s",
obj.Status.History.Latest().FullReleaseName(), changeSet.String())
}
diff --git a/internal/reconcile/install.go b/internal/reconcile/install.go
index 0567b549d..367beb7ab 100644
--- a/internal/reconcile/install.go
+++ b/internal/reconcile/install.go
@@ -92,7 +92,7 @@ func (r *Install) Reconcile(ctx context.Context, req *Request) error {
_, err := action.Install(ctx, cfg, req.Object, req.Chart, req.Values)
// Record the history of releases observed during the install.
- obsReleases.recordOnObject(req.Object)
+ obsReleases.recordOnObject(req.Object, mutateOciDigest)
if err != nil {
r.failure(req, logBuf, err)
@@ -154,7 +154,8 @@ func (r *Install) failure(req *Request, buffer *action.LogBuffer, err error) {
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String()),
+ eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String(),
+ addOciDigest(req.Object.Status.LastAttemptedRevisionDigest)),
corev1.EventTypeWarning,
v2.InstallFailedReason,
eventMessageWithLog(msg, buffer),
@@ -181,7 +182,7 @@ func (r *Install) success(req *Request) {
// Record event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeNormal,
v2.InstallSucceededReason,
msg,
diff --git a/internal/reconcile/install_test.go b/internal/reconcile/install_test.go
index d156e45ca..4b4fe3efe 100644
--- a/internal/reconcile/install_test.go
+++ b/internal/reconcile/install_test.go
@@ -307,6 +307,9 @@ func TestInstall_failure(t *testing.T) {
ReleaseName: mockReleaseName,
TargetNamespace: mockReleaseNamespace,
},
+ Status: v2.HelmReleaseStatus{
+ LastAttemptedRevisionDigest: "sha256:1234567890",
+ },
}
chrt = testutil.BuildChart()
err = errors.New("installation error")
@@ -337,6 +340,7 @@ func TestInstall_failure(t *testing.T) {
Message: expectMsg,
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
+ eventMetaGroupKey(metaOCIDigestKey): obj.Status.LastAttemptedRevisionDigest,
eventMetaGroupKey(eventv1.MetaRevisionKey): chrt.Metadata.Version,
eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, req.Values).String(),
},
diff --git a/internal/reconcile/release.go b/internal/reconcile/release.go
index ce0a74d50..89239c419 100644
--- a/internal/reconcile/release.go
+++ b/internal/reconcile/release.go
@@ -43,6 +43,10 @@ var (
ErrReleaseMismatch = errors.New("release mismatch")
)
+// mutateObservedRelease is a function that mutates the Observation with the
+// given HelmRelease object.
+type mutateObservedRelease func(*v2.HelmRelease, release.Observation) release.Observation
+
// observedReleases is a map of Helm releases as observed to be written to the
// Helm storage. The key is the version of the release.
type observedReleases map[int]release.Observation
@@ -58,7 +62,7 @@ func (r observedReleases) sortedVersions() (versions []int) {
}
// recordOnObject records the observed releases on the HelmRelease object.
-func (r observedReleases) recordOnObject(obj *v2.HelmRelease) {
+func (r observedReleases) recordOnObject(obj *v2.HelmRelease, mutators ...mutateObservedRelease) {
switch len(r) {
case 0:
return
@@ -67,17 +71,27 @@ func (r observedReleases) recordOnObject(obj *v2.HelmRelease) {
for _, o := range r {
obs = o
}
+ for _, mut := range mutators {
+ obs = mut(obj, obs)
+ }
obj.Status.History = append(v2.Snapshots{release.ObservedToSnapshot(obs)}, obj.Status.History...)
default:
versions := r.sortedVersions()
-
- obj.Status.History = append(v2.Snapshots{release.ObservedToSnapshot(r[versions[0]])}, obj.Status.History...)
+ obs := r[versions[0]]
+ for _, mut := range mutators {
+ obs = mut(obj, obs)
+ }
+ obj.Status.History = append(v2.Snapshots{release.ObservedToSnapshot(obs)}, obj.Status.History...)
for _, ver := range versions[1:] {
for i := range obj.Status.History {
snap := obj.Status.History[i]
if snap.Targets(r[ver].Name, r[ver].Namespace, r[ver].Version) {
- newSnap := release.ObservedToSnapshot(r[ver])
+ obs := r[ver]
+ for _, mut := range mutators {
+ obs = mut(obj, obs)
+ }
+ newSnap := release.ObservedToSnapshot(obs)
newSnap.SetTestHooks(snap.GetTestHooks())
obj.Status.History[i] = newSnap
return
@@ -87,6 +101,11 @@ func (r observedReleases) recordOnObject(obj *v2.HelmRelease) {
}
}
+func mutateOciDigest(obj *v2.HelmRelease, obs release.Observation) release.Observation {
+ obs.OciDigest = obj.Status.LastAttemptedRevisionDigest
+ 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
@@ -174,9 +193,15 @@ func eventMessageWithLog(msg string, log *action.LogBuffer) string {
return msg
}
+// addMeta is a function that adds metadata to an event map.
+type addMeta func(map[string]string)
+
+// metaOCIDigestKey is the key for the OCI digest metadata.
+const metaOCIDigestKey = "oci-digest"
+
// eventMeta returns the event (annotation) metadata based on the given
// parameters.
-func eventMeta(revision, token string) map[string]string {
+func eventMeta(revision, token string, metas ...addMeta) map[string]string {
var metadata map[string]string
if revision != "" || token != "" {
metadata = make(map[string]string)
@@ -187,9 +212,25 @@ func eventMeta(revision, token string) map[string]string {
metadata[eventMetaGroupKey(eventv1.MetaTokenKey)] = token
}
}
+
+ for _, add := range metas {
+ add(metadata)
+ }
+
return metadata
}
+func addOciDigest(digest string) addMeta {
+ return func(m map[string]string) {
+ if digest != "" {
+ if m == nil {
+ m = make(map[string]string)
+ }
+ m[eventMetaGroupKey(metaOCIDigestKey)] = digest
+ }
+ }
+}
+
// eventMetaGroupKey returns the event (annotation) metadata key prefixed with
// the group.
func eventMetaGroupKey(key string) string {
diff --git a/internal/reconcile/release_test.go b/internal/reconcile/release_test.go
index 167b8df72..d6ebaa7c5 100644
--- a/internal/reconcile/release_test.go
+++ b/internal/reconcile/release_test.go
@@ -17,10 +17,12 @@ limitations under the License.
package reconcile
import (
+ "fmt"
"testing"
"github.com/go-logr/logr"
. "github.com/onsi/gomega"
+ "helm.sh/helm/v3/pkg/chart"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
@@ -454,3 +456,179 @@ func mockLogBuffer(size int, lines int) *action.LogBuffer {
}
return log
}
+
+func Test_RecordOnObject(t *testing.T) {
+ tests := []struct {
+ name string
+ obj *v2.HelmRelease
+ r observedReleases
+ mutate bool
+ testFunc func(*v2.HelmRelease) error
+ }{
+ {
+ name: "record observed releases",
+ obj: &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: mockReleaseName,
+ Namespace: mockReleaseNamespace,
+ },
+ },
+ r: observedReleases{
+ 1: {
+ Name: mockReleaseName,
+ Version: 1,
+ ChartMetadata: chart.Metadata{
+ Name: mockReleaseName,
+ Version: "1.0.0",
+ },
+ },
+ },
+ testFunc: func(obj *v2.HelmRelease) error {
+ if len(obj.Status.History) != 1 {
+ return fmt.Errorf("history length is not 1")
+ }
+ if obj.Status.History[0].Name != mockReleaseName {
+ return fmt.Errorf("release name is not %s", mockReleaseName)
+ }
+ return nil
+ },
+ },
+ {
+ name: "record observed releases with multiple versions",
+ obj: &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: mockReleaseName,
+ Namespace: mockReleaseNamespace,
+ },
+ },
+ r: observedReleases{
+ 1: {
+ Name: mockReleaseName,
+ Version: 1,
+ ChartMetadata: chart.Metadata{
+ Name: mockReleaseName,
+ Version: "1.0.0",
+ },
+ },
+ 2: {
+ Name: mockReleaseName,
+ Version: 2,
+ ChartMetadata: chart.Metadata{
+ Name: mockReleaseName,
+ Version: "2.0.0",
+ },
+ },
+ },
+ testFunc: func(obj *v2.HelmRelease) error {
+ if len(obj.Status.History) != 1 {
+ return fmt.Errorf("want history length 1, got %d", len(obj.Status.History))
+ }
+ if obj.Status.History[0].Name != mockReleaseName {
+ return fmt.Errorf("release name is not %s", mockReleaseName)
+ }
+ if obj.Status.History[0].ChartVersion != "2.0.0" {
+ return fmt.Errorf("want chart version %s, got %s", "2.0.0", obj.Status.History[0].ChartVersion)
+ }
+ return nil
+ },
+ },
+ {
+ name: "record observed releases with status digest",
+ obj: &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: mockReleaseName,
+ Namespace: mockReleaseNamespace,
+ },
+ Status: v2.HelmReleaseStatus{
+ LastAttemptedRevisionDigest: "sha256:123456",
+ },
+ },
+ r: observedReleases{
+ 1: {
+ Name: mockReleaseName,
+ Version: 1,
+ ChartMetadata: chart.Metadata{
+ Name: mockReleaseName,
+ Version: "1.0.0",
+ },
+ },
+ },
+ mutate: true,
+ testFunc: func(obj *v2.HelmRelease) error {
+ h := obj.Status.History.Latest()
+ if h.Name != mockReleaseName {
+ return fmt.Errorf("release name is not %s", mockReleaseName)
+ }
+ if h.ChartVersion != "1.0.0" {
+ return fmt.Errorf("want chart version %s, got %s", "1.0.0", h.ChartVersion)
+ }
+ if h.OciDigest != obj.Status.LastAttemptedRevisionDigest {
+ return fmt.Errorf("want digest %s, got %s", obj.Status.LastAttemptedRevisionDigest, h.OciDigest)
+ }
+ return nil
+ },
+ },
+ {
+ name: "record observed releases with multiple versions and status digest",
+ obj: &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: mockReleaseName,
+ Namespace: mockReleaseNamespace,
+ },
+ Status: v2.HelmReleaseStatus{
+ LastAttemptedRevisionDigest: "sha256:123456",
+ },
+ },
+ r: observedReleases{
+ 1: {
+ Name: mockReleaseName,
+ Version: 1,
+ ChartMetadata: chart.Metadata{
+ Name: mockReleaseName,
+ Version: "1.0.0",
+ },
+ },
+ 2: {
+ Name: mockReleaseName,
+ Version: 2,
+ ChartMetadata: chart.Metadata{
+ Name: mockReleaseName,
+ Version: "2.0.0",
+ },
+ },
+ },
+ mutate: true,
+ testFunc: func(obj *v2.HelmRelease) error {
+ if len(obj.Status.History) != 1 {
+ return fmt.Errorf("want history length 1, got %d", len(obj.Status.History))
+ }
+ h := obj.Status.History.Latest()
+ if h.Name != mockReleaseName {
+ return fmt.Errorf("release name is not %s", mockReleaseName)
+ }
+ if h.ChartVersion != "2.0.0" {
+ return fmt.Errorf("want chart version %s, got %s", "2.0.0", h.ChartVersion)
+ }
+ if h.OciDigest != obj.Status.LastAttemptedRevisionDigest {
+ return fmt.Errorf("want digest %s, got %s", obj.Status.LastAttemptedRevisionDigest, h.OciDigest)
+ }
+ return nil
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ if tt.mutate {
+ tt.r.recordOnObject(tt.obj, mutateOciDigest)
+ } else {
+ tt.r.recordOnObject(tt.obj)
+ }
+ err := tt.testFunc(tt.obj)
+ g.Expect(err).ToNot(HaveOccurred())
+ })
+ }
+
+}
diff --git a/internal/reconcile/rollback_remediation.go b/internal/reconcile/rollback_remediation.go
index e614f5a30..d0073a8ef 100644
--- a/internal/reconcile/rollback_remediation.go
+++ b/internal/reconcile/rollback_remediation.go
@@ -143,7 +143,7 @@ func (r *RollbackRemediation) failure(req *Request, prev *v2.Snapshot, buffer *a
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String()),
+ eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String(), addOciDigest(prev.OciDigest)),
corev1.EventTypeWarning,
v2.RollbackFailedReason,
eventMessageWithLog(msg, buffer),
@@ -162,7 +162,7 @@ func (r *RollbackRemediation) success(req *Request, prev *v2.Snapshot) {
// Record event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String()),
+ eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String(), addOciDigest(prev.OciDigest)),
corev1.EventTypeNormal,
v2.RollbackSucceededReason,
msg,
diff --git a/internal/reconcile/test.go b/internal/reconcile/test.go
index f40a3d4cf..49a5d91cc 100644
--- a/internal/reconcile/test.go
+++ b/internal/reconcile/test.go
@@ -145,7 +145,7 @@ func (r *Test) failure(req *Request, err error) {
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeWarning,
v2.TestFailedReason,
msg,
@@ -181,7 +181,7 @@ func (r *Test) success(req *Request) {
// Record event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeNormal,
v2.TestSucceededReason,
msg,
diff --git a/internal/reconcile/uninstall.go b/internal/reconcile/uninstall.go
index 8c8158745..f8721b832 100644
--- a/internal/reconcile/uninstall.go
+++ b/internal/reconcile/uninstall.go
@@ -180,7 +180,7 @@ func (r *Uninstall) failure(req *Request, buffer *action.LogBuffer, err error) {
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeWarning, v2.UninstallFailedReason,
eventMessageWithLog(msg, buffer),
)
@@ -201,7 +201,7 @@ func (r *Uninstall) success(req *Request) {
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeNormal,
v2.UninstallSucceededReason,
msg,
diff --git a/internal/reconcile/uninstall_remediation.go b/internal/reconcile/uninstall_remediation.go
index 4e244cdc0..34798cff0 100644
--- a/internal/reconcile/uninstall_remediation.go
+++ b/internal/reconcile/uninstall_remediation.go
@@ -154,7 +154,7 @@ func (r *UninstallRemediation) failure(req *Request, buffer *action.LogBuffer, e
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeWarning,
v2.UninstallFailedReason,
eventMessageWithLog(msg, buffer),
@@ -175,7 +175,7 @@ func (r *UninstallRemediation) success(req *Request) {
// Record event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeNormal,
v2.UninstallSucceededReason,
msg,
diff --git a/internal/reconcile/unlock.go b/internal/reconcile/unlock.go
index 7d045856c..a1dc5fded 100644
--- a/internal/reconcile/unlock.go
+++ b/internal/reconcile/unlock.go
@@ -119,7 +119,7 @@ func (r *Unlock) failure(req *Request, cur *v2.Snapshot, status helmrelease.Stat
// Record warning event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeWarning,
"PendingRelease",
msg,
@@ -138,7 +138,7 @@ func (r *Unlock) success(req *Request, cur *v2.Snapshot, status helmrelease.Stat
// Record event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeNormal,
"PendingRelease",
msg,
diff --git a/internal/reconcile/upgrade.go b/internal/reconcile/upgrade.go
index 8cdbb0828..6a2a01ff1 100644
--- a/internal/reconcile/upgrade.go
+++ b/internal/reconcile/upgrade.go
@@ -83,7 +83,7 @@ func (r *Upgrade) Reconcile(ctx context.Context, req *Request) error {
_, err := action.Upgrade(ctx, cfg, req.Object, req.Chart, req.Values)
// Record the history of releases observed during the upgrade.
- obsReleases.recordOnObject(req.Object)
+ obsReleases.recordOnObject(req.Object, mutateOciDigest)
if err != nil {
r.failure(req, logBuf, err)
@@ -144,7 +144,8 @@ func (r *Upgrade) failure(req *Request, buffer *action.LogBuffer, err error) {
// Condition summary.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String()),
+ eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String(),
+ addOciDigest(req.Object.Status.LastAttemptedRevisionDigest)),
corev1.EventTypeWarning,
v2.UpgradeFailedReason,
eventMessageWithLog(msg, buffer),
@@ -171,7 +172,7 @@ func (r *Upgrade) success(req *Request) {
// Record event.
r.eventRecorder.AnnotatedEventf(
req.Object,
- eventMeta(cur.ChartVersion, cur.ConfigDigest),
+ eventMeta(cur.ChartVersion, cur.ConfigDigest, addOciDigest(cur.OciDigest)),
corev1.EventTypeNormal,
v2.UpgradeSucceededReason,
msg,
diff --git a/internal/reconcile/upgrade_test.go b/internal/reconcile/upgrade_test.go
index dbfb85b7c..08bc98632 100644
--- a/internal/reconcile/upgrade_test.go
+++ b/internal/reconcile/upgrade_test.go
@@ -438,6 +438,9 @@ func TestUpgrade_failure(t *testing.T) {
ReleaseName: mockReleaseName,
TargetNamespace: mockReleaseNamespace,
},
+ Status: v2.HelmReleaseStatus{
+ LastAttemptedRevisionDigest: "sha256:1234567890",
+ },
}
chrt = testutil.BuildChart()
err = errors.New("upgrade error")
@@ -468,6 +471,7 @@ func TestUpgrade_failure(t *testing.T) {
Message: expectMsg,
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
+ eventMetaGroupKey(metaOCIDigestKey): obj.Status.LastAttemptedRevisionDigest,
eventMetaGroupKey(eventv1.MetaRevisionKey): chrt.Metadata.Version,
eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, req.Values).String(),
},
diff --git a/internal/release/digest_test.go b/internal/release/digest_test.go
index c340ca965..3aca5b8eb 100644
--- a/internal/release/digest_test.go
+++ b/internal/release/digest_test.go
@@ -37,7 +37,7 @@ func TestDigest(t *testing.T) {
rel: Observation{
Name: "foo",
},
- exp: "sha256:91b6773f7696d3eb405708a07e2daedc6e69664dabac8e10af7d570d09f947d5",
+ exp: "sha256:ca8901e499a79368647134cc4f1c2118f8e7ec64c8a4703b281d17fb01acfbed",
},
}
for _, tt := range tests {
diff --git a/internal/release/observation.go b/internal/release/observation.go
index 35c0a4340..9fe5b9ead 100644
--- a/internal/release/observation.go
+++ b/internal/release/observation.go
@@ -79,6 +79,8 @@ type Observation struct {
Hooks []helmrelease.Hook `json:"hooks"`
// Namespace is the Kubernetes namespace of the release.
Namespace string `json:"namespace"`
+ // OciDigest is the digest of the OCI artifact that was used to
+ OciDigest string `json:"ociDigest"`
}
// Targets returns if the release matches the given name, namespace and
@@ -166,6 +168,7 @@ func ObservedToSnapshot(rls Observation) *v2.Snapshot {
LastDeployed: metav1.NewTime(rls.Info.LastDeployed.Time),
Deleted: metav1.NewTime(rls.Info.Deleted.Time),
Status: rls.Info.Status.String(),
+ OciDigest: rls.OciDigest,
}
}