Take into account the oci-digest

This commit add the oci artifact digest into the release observed
snapshot. This is used to later to add that value as an annotation.

Signed-off-by: Soule BA <bah.soule@gmail.com>
This commit is contained in:
Soule BA 2024-04-04 11:30:59 +02:00
parent 49b47d4c44
commit edec322a3d
No known key found for this signature in database
GPG Key ID: 4D40965192802994
22 changed files with 336 additions and 52 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -1584,12 +1584,25 @@ string
<td>
<em>(Optional)</em>
<p>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. &ldquo;1.2.3+1234567890ab&rdquo;.</p>
</td>
</tr>
<tr>
<td>
<code>lastAttemptedRevisionDigest</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>LastAttemptedRevisionDigest is the digest of the last reconciliation attempt.
This is only set for OCIRepository sources.</p>
</td>
</tr>
<tr>
<td>
<code>lastAttemptedValuesChecksum</code><br>
<em>
string
@ -2380,6 +2393,18 @@ TestHookStatus
run by the controller.</p>
</td>
</tr>
<tr>
<td>
<code>ociDigest</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>OCIDigest is the digest of the OCI artifact associated with the release.</p>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -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
```

View File

@ -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.

View File

@ -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 <tag>@<digest> 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 = tagD[1]
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 = revision
}
chart.Metadata.Version = ver.String()
return nil
return ociDigest, nil
}

View File

@ -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 {

View File

@ -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())
}

View File

@ -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,

View File

@ -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(),
},

View File

@ -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,25 @@ 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]
obs.OCIDigest = snap.OCIDigest
newSnap := release.ObservedToSnapshot(obs)
newSnap.SetTestHooks(snap.GetTestHooks())
obj.Status.History[i] = newSnap
return
@ -87,6 +99,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 +191,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 +210,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 {

View File

@ -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())
})
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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(),
},

View File

@ -37,7 +37,7 @@ func TestDigest(t *testing.T) {
rel: Observation{
Name: "foo",
},
exp: "sha256:91b6773f7696d3eb405708a07e2daedc6e69664dabac8e10af7d570d09f947d5",
exp: "sha256:ca8901e499a79368647134cc4f1c2118f8e7ec64c8a4703b281d17fb01acfbed",
},
}
for _, tt := range tests {

View File

@ -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,
}
}