Skip to content

Commit ca14945

Browse files
committed
Add OCI revision to events
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent 550576e commit ca14945

File tree

8 files changed

+156
-19
lines changed

8 files changed

+156
-19
lines changed

api/v1/kustomization_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ type KustomizationStatus struct {
263263
// +optional
264264
LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`
265265

266+
// The last successfully applied origin revision.
267+
// Equals the origin revision of the applied Artifact from the referenced Source.
268+
// Usually present on the Metadata of the applied Artifact and depends on the
269+
// Source type, e.g. for OCI it's the value associated with the key
270+
// "org.opencontainers.image.revision".
271+
// +optional
272+
LastAppliedOriginRevision string `json:"lastAppliedOriginRevision,omitempty"`
273+
266274
// LastAttemptedRevision is the revision of the last reconciliation attempt.
267275
// +optional
268276
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`

config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,14 @@ spec:
520520
required:
521521
- entries
522522
type: object
523+
lastAppliedOriginRevision:
524+
description: |-
525+
The last successfully applied origin revision.
526+
Equals the origin revision of the applied Artifact from the referenced Source.
527+
Usually present on the Metadata of the applied Artifact and depends on the
528+
Source type, e.g. for OCI it's the value associated with the key
529+
"org.opencontainers.image.revision".
530+
type: string
523531
lastAppliedRevision:
524532
description: |-
525533
The last successfully applied revision.

docs/api/v1/kustomize.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,22 @@ Equals the Revision of the applied Artifact from the referenced Source.</p>
994994
</tr>
995995
<tr>
996996
<td>
997+
<code>lastAppliedOriginRevision</code><br>
998+
<em>
999+
string
1000+
</em>
1001+
</td>
1002+
<td>
1003+
<em>(Optional)</em>
1004+
<p>The last successfully applied origin revision.
1005+
Equals the origin revision of the applied Artifact from the referenced Source.
1006+
Usually present on the Metadata of the applied Artifact and depends on the
1007+
Source type, e.g. for OCI it&rsquo;s the value associated with the key
1008+
&ldquo;org.opencontainers.image.revision&rdquo;.</p>
1009+
</td>
1010+
</tr>
1011+
<tr>
1012+
<td>
9971013
<code>lastAttemptedRevision</code><br>
9981014
<em>
9991015
string

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ require (
1919
github.com/fluxcd/cli-utils v0.36.0-flux.11
2020
github.com/fluxcd/kustomize-controller/api v1.4.0
2121
github.com/fluxcd/pkg/apis/acl v0.5.0
22-
github.com/fluxcd/pkg/apis/event v0.13.0
22+
github.com/fluxcd/pkg/apis/event v0.15.0
2323
github.com/fluxcd/pkg/apis/kustomize v1.8.0
2424
github.com/fluxcd/pkg/apis/meta v1.9.0
2525
github.com/fluxcd/pkg/http/fetch v0.14.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ github.com/fluxcd/cli-utils v0.36.0-flux.11 h1:W0y2uvCVkcE8bgV9jgoGSjzWbLFiNq1Aj
181181
github.com/fluxcd/cli-utils v0.36.0-flux.11/go.mod h1:WZ7xUpZbK+O6HBxA5UWqzWTLSSltdmj4wS1LstS5Dqs=
182182
github.com/fluxcd/pkg/apis/acl v0.5.0 h1:+ykKezgerKUlZwSYFUy03lPMOIAyWlqvMNNLIWWqOhk=
183183
github.com/fluxcd/pkg/apis/acl v0.5.0/go.mod h1:IVDZx3MAoDWjlLrJHMF9Z27huFuXAEQlnbWw0M6EcTs=
184-
github.com/fluxcd/pkg/apis/event v0.13.0 h1:m5qHAhYIC0+mRFy5OC8FZxBVBGJM3qxJ/sEg2Vgx4T8=
185-
github.com/fluxcd/pkg/apis/event v0.13.0/go.mod h1:aRK2AONnjjSNW61B6Iy3SW4YHozACntnJeGm3fFqDqA=
184+
github.com/fluxcd/pkg/apis/event v0.15.0 h1:k1suqIfVxnhEeKlGkvlHAbOYXjY8wRixT/OZcIuakqA=
185+
github.com/fluxcd/pkg/apis/event v0.15.0/go.mod h1:aRK2AONnjjSNW61B6Iy3SW4YHozACntnJeGm3fFqDqA=
186186
github.com/fluxcd/pkg/apis/kustomize v1.8.0 h1:HH6YRa3SMS72KK4cUyb9m5sK/dZH+Eti1qhjWDCgwKg=
187187
github.com/fluxcd/pkg/apis/kustomize v1.8.0/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0=
188188
github.com/fluxcd/pkg/apis/meta v1.9.0 h1:wPgm7bWNJZ/ImS5GqikOxt362IgLPFBG73dZ27uWRiQ=

internal/controller/kustomization_controller.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import (
7070
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
7171
"github.com/fluxcd/kustomize-controller/internal/decryptor"
7272
"github.com/fluxcd/kustomize-controller/internal/inventory"
73+
"github.com/fluxcd/kustomize-controller/internal/pkg"
7374
)
7475

7576
// +kubebuilder:rbac:groups=kustomize.toolkit.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
@@ -193,7 +194,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
193194
time.Since(reconcileStart).String(),
194195
obj.Spec.Interval.Duration.String())
195196
log.Info(msg, "revision", obj.Status.LastAttemptedRevision)
196-
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityInfo, msg,
197+
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityInfo, msg,
197198
map[string]string{
198199
kustomizev1.GroupVersion.Group + "/" + eventv1.MetaCommitStatusKey: eventv1.MetaCommitStatusUpdateValue,
199200
})
@@ -234,7 +235,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
234235
if acl.IsAccessDenied(err) {
235236
conditions.MarkFalse(obj, meta.ReadyCondition, apiacl.AccessDeniedReason, "%s", err)
236237
log.Error(err, "Access denied to cross-namespace source")
237-
r.event(obj, "unknown", eventv1.EventSeverityError, err.Error(), nil)
238+
r.event(obj, "", "", eventv1.EventSeverityError, err.Error(), nil)
238239
return ctrl.Result{RequeueAfter: obj.GetRetryInterval()}, nil
239240
}
240241

@@ -249,14 +250,16 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
249250
log.Info(msg)
250251
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
251252
}
253+
revision := artifactSource.GetArtifact().Revision
254+
originRevision := getOriginRevision(artifactSource)
252255

253256
// Check dependencies and requeue the reconciliation if the check fails.
254257
if len(obj.Spec.DependsOn) > 0 {
255258
if err := r.checkDependencies(ctx, obj, artifactSource); err != nil {
256259
conditions.MarkFalse(obj, meta.ReadyCondition, meta.DependencyNotReadyReason, "%s", err)
257260
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s", r.requeueDependency.String())
258261
log.Info(msg)
259-
r.event(obj, artifactSource.GetArtifact().Revision, eventv1.EventSeverityInfo, msg, nil)
262+
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
260263
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
261264
}
262265
log.Info("All dependencies are ready, proceeding with reconciliation")
@@ -279,8 +282,8 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
279282
time.Since(reconcileStart).String(),
280283
obj.GetRetryInterval().String()),
281284
"revision",
282-
artifactSource.GetArtifact().Revision)
283-
r.event(obj, artifactSource.GetArtifact().Revision, eventv1.EventSeverityError,
285+
revision)
286+
r.event(obj, revision, originRevision, eventv1.EventSeverityError,
284287
reconcileErr.Error(), nil)
285288
return ctrl.Result{RequeueAfter: obj.GetRetryInterval()}, nil
286289
}
@@ -298,6 +301,7 @@ func (r *KustomizationReconciler) reconcile(
298301

299302
// Update status with the reconciliation progress.
300303
revision := src.GetArtifact().Revision
304+
originRevision := getOriginRevision(src)
301305
progressingMsg := fmt.Sprintf("Fetching manifests for revision %s with a timeout of %s", revision, obj.GetTimeout().String())
302306
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "%s", "Reconciliation in progress")
303307
conditions.MarkReconciling(obj, meta.ProgressingReason, "%s", progressingMsg)
@@ -419,7 +423,7 @@ func (r *KustomizationReconciler) reconcile(
419423
}
420424

421425
// Validate and apply resources in stages.
422-
drifted, changeSet, err := r.apply(ctx, resourceManager, obj, revision, objects)
426+
drifted, changeSet, err := r.apply(ctx, resourceManager, obj, revision, originRevision, objects)
423427
if err != nil {
424428
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, "%s", err)
425429
return err
@@ -444,7 +448,7 @@ func (r *KustomizationReconciler) reconcile(
444448
}
445449

446450
// Run garbage collection for stale resources that do not have pruning disabled.
447-
if _, err := r.prune(ctx, resourceManager, obj, revision, staleObjects); err != nil {
451+
if _, err := r.prune(ctx, resourceManager, obj, revision, originRevision, staleObjects); err != nil {
448452
conditions.MarkFalse(obj, meta.ReadyCondition, meta.PruneFailedReason, "%s", err)
449453
return err
450454
}
@@ -456,15 +460,17 @@ func (r *KustomizationReconciler) reconcile(
456460
patcher,
457461
obj,
458462
revision,
463+
originRevision,
459464
isNewRevision,
460465
drifted,
461466
changeSet.ToObjMetadataSet()); err != nil {
462467
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, "%s", err)
463468
return err
464469
}
465470

466-
// Set last applied revision.
471+
// Set last applied revisions.
467472
obj.Status.LastAppliedRevision = revision
473+
obj.Status.LastAppliedOriginRevision = originRevision
468474

469475
// Mark the object as ready.
470476
conditions.MarkTrue(obj,
@@ -656,6 +662,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
656662
manager *ssa.ResourceManager,
657663
obj *kustomizev1.Kustomization,
658664
revision string,
665+
originRevision string,
659666
objects []*unstructured.Unstructured) (bool, *ssa.ChangeSet, error) {
660667
log := ctrl.LoggerFrom(ctx)
661668

@@ -841,7 +848,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
841848
// emit event only if the server-side apply resulted in changes
842849
applyLog := strings.TrimSuffix(changeSetLog.String(), "\n")
843850
if applyLog != "" {
844-
r.event(obj, revision, eventv1.EventSeverityInfo, applyLog, nil)
851+
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, applyLog, nil)
845852
}
846853

847854
return applyLog != "", resultSet, nil
@@ -852,6 +859,7 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context,
852859
patcher *patch.SerialPatcher,
853860
obj *kustomizev1.Kustomization,
854861
revision string,
862+
originRevision string,
855863
isNewRevision bool,
856864
drifted bool,
857865
objects object.ObjMetadataSet) error {
@@ -910,7 +918,7 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context,
910918
// Emit recovery event if the previous health check failed.
911919
msg := fmt.Sprintf("Health check passed in %s", time.Since(checkStart).String())
912920
if !wasHealthy || (isNewRevision && drifted) {
913-
r.event(obj, revision, eventv1.EventSeverityInfo, msg, nil)
921+
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
914922
}
915923

916924
conditions.MarkTrue(obj, meta.HealthyCondition, meta.SucceededReason, "%s", msg)
@@ -925,6 +933,7 @@ func (r *KustomizationReconciler) prune(ctx context.Context,
925933
manager *ssa.ResourceManager,
926934
obj *kustomizev1.Kustomization,
927935
revision string,
936+
originRevision string,
928937
objects []*unstructured.Unstructured) (bool, error) {
929938
if !obj.Spec.Prune {
930939
return false, nil
@@ -949,7 +958,7 @@ func (r *KustomizationReconciler) prune(ctx context.Context,
949958
// emit event only if the prune operation resulted in changes
950959
if changeSet != nil && len(changeSet.Entries) > 0 {
951960
log.Info(fmt.Sprintf("garbage collection completed: %s", changeSet.String()))
952-
r.event(obj, revision, eventv1.EventSeverityInfo, changeSet.String(), nil)
961+
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
953962
return true, nil
954963
}
955964

@@ -1004,19 +1013,19 @@ func (r *KustomizationReconciler) finalize(ctx context.Context,
10041013

10051014
changeSet, err := resourceManager.DeleteAll(ctx, objects, opts)
10061015
if err != nil {
1007-
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityError, "pruning for deleted resource failed", nil)
1016+
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityError, "pruning for deleted resource failed", nil)
10081017
// Return the error so we retry the failed garbage collection
10091018
return ctrl.Result{}, err
10101019
}
10111020

10121021
if changeSet != nil && len(changeSet.Entries) > 0 {
1013-
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
1022+
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
10141023
}
10151024
} else {
10161025
// when the account to impersonate is gone, log the stale objects and continue with the finalization
10171026
msg := fmt.Sprintf("unable to prune objects: \n%s", ssautil.FmtUnstructuredList(objects))
10181027
log.Error(fmt.Errorf("skiping pruning, failed to find account to impersonate"), msg)
1019-
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityError, msg, nil)
1028+
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityError, msg, nil)
10201029
}
10211030
}
10221031

@@ -1027,13 +1036,16 @@ func (r *KustomizationReconciler) finalize(ctx context.Context,
10271036
}
10281037

10291038
func (r *KustomizationReconciler) event(obj *kustomizev1.Kustomization,
1030-
revision, severity, msg string,
1039+
revision, originRevision, severity, msg string,
10311040
metadata map[string]string) {
10321041
if metadata == nil {
10331042
metadata = map[string]string{}
10341043
}
10351044
if revision != "" {
1036-
metadata[kustomizev1.GroupVersion.Group+"/revision"] = revision
1045+
metadata[kustomizev1.GroupVersion.Group+"/"+eventv1.MetaRevisionKey] = revision
1046+
}
1047+
if originRevision != "" {
1048+
metadata[kustomizev1.GroupVersion.Group+"/"+eventv1.MetaOriginRevisionKey] = originRevision
10371049
}
10381050

10391051
reason := severity
@@ -1108,3 +1120,14 @@ func (r *KustomizationReconciler) patch(ctx context.Context,
11081120

11091121
return nil
11101122
}
1123+
1124+
// getOriginRevision returns the origin revision of the source artifact,
1125+
// or the empty string if it's not present, or if the artifact itself
1126+
// is not present.
1127+
func getOriginRevision(src sourcev1.Source) string {
1128+
a := src.GetArtifact()
1129+
if a == nil {
1130+
return ""
1131+
}
1132+
return a.Metadata[pkg.OCIArtifactOriginRevisionAnnotation]
1133+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright 2025 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/gomega"
23+
"k8s.io/client-go/tools/record"
24+
25+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
26+
)
27+
28+
func TestKustomizationReconciler_event(t *testing.T) {
29+
for _, tt := range []struct {
30+
name string
31+
originRevision string
32+
event string
33+
}{
34+
{
35+
name: "non-empty origin revision is present on event",
36+
originRevision: "orev",
37+
event: "Normal info message map[kustomize.toolkit.fluxcd.io/originRevision:orev]",
38+
},
39+
{
40+
name: "empty origin revision is not present on event",
41+
originRevision: "",
42+
event: "Normal info message",
43+
},
44+
} {
45+
t.Run(tt.name, func(t *testing.T) {
46+
g := NewWithT(t)
47+
48+
recorder := record.NewFakeRecorder(1)
49+
r := &KustomizationReconciler{EventRecorder: recorder}
50+
51+
obj := &kustomizev1.Kustomization{}
52+
53+
r.event(obj, "", tt.originRevision, "info", "message", nil)
54+
55+
select {
56+
case event := <-recorder.Events:
57+
g.Expect(event).To(Equal(tt.event))
58+
default:
59+
t.Fatal("expected event")
60+
}
61+
})
62+
}
63+
}

internal/pkg/artifact.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
Copyright 2025 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package pkg
18+
19+
const OCIArtifactOriginRevisionAnnotation = "org.opencontainers.image.revision"

0 commit comments

Comments
 (0)