Skip to content

Commit 7a04844

Browse files
committed
Add deprecation handling for .Updated template field
Implement error handling for deprecated .Updated template field usage in ImageUpdateAutomation commit message templates. When users attempt to use deprecated .Updated fields, the controller now sets a Stalled condition with a clear error message directing them to use .Changed fields instead, preventing infinite reconciliation loops. This prepares for the eventual removal of the .Updated field from the ImageUpdateAutomation API as part of GA preparation. Signed-off-by: cappyzawa <[email protected]>
1 parent c77a979 commit 7a04844

File tree

6 files changed

+129
-20
lines changed

6 files changed

+129
-20
lines changed

api/v1beta2/condition_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,7 @@ const (
3636

3737
// InvalidPolicySelectorReason represents an invalid policy selector.
3838
InvalidPolicySelectorReason string = "InvalidPolicySelector"
39+
40+
// DeprecatedTemplateFieldReason represents usage of deprecated template field.
41+
DeprecatedTemplateFieldReason string = "DeprecatedTemplateField"
3942
)

docs/api/v1beta2/image-automation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
735735
</div>
736736
</div>
737737
<h3 id="image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">ObservedPolicies
738-
(<code>map[string]./api/v1beta2.ImageRef</code> alias)</h3>
738+
(<code>map[string]github.com/fluxcd/image-automation-controller/api/v1beta2.ImageRef</code> alias)</h3>
739739
<p>
740740
(<em>Appears on:</em>
741741
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus</a>)

internal/controller/imageupdateautomation_controller.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,15 @@ func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *pat
481481

482482
pushResult, err = sm.CommitAndPush(ctx, obj, policyResult, pushCfg...)
483483
if err != nil {
484+
// Check if error is due to deprecated template field usage.
485+
// Set Stalled condition and return nil error to prevent requeue, allowing user to fix template.
486+
// TODO: Remove this block after .Updated template field is completely removed from the API.
487+
if errors.Is(err, source.ErrDeprecatedTemplateField) {
488+
conditions.MarkStalled(obj, imagev1.DeprecatedTemplateFieldReason, "%s", err)
489+
result, retErr = ctrl.Result{}, nil
490+
return
491+
}
492+
484493
e := fmt.Errorf("failed to update source: %w", err)
485494
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason, "%s", e)
486495
result, retErr = ctrl.Result{}, e

internal/controller/imageupdateautomation_controller_test.go

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ const (
7171
Automation: {{ .AutomationObject }}
7272
7373
Files:
74-
{{ range $filename, $_ := .Updated.Files -}}
74+
{{ range $filename, $_ := .Changed.ImageResult.Files -}}
7575
- {{ $filename }}
7676
{{ end -}}
7777
7878
Objects:
79-
{{ range $resource, $_ := .Updated.Objects -}}
79+
{{ range $resource, $_ := .Changed.ImageResult.Objects -}}
8080
{{ if eq $resource.Kind "Deployment" -}}
8181
- {{ $resource.Kind | lower }} {{ $resource.Name | lower }}
8282
{{ else -}}
@@ -85,7 +85,7 @@ Objects:
8585
{{ end -}}
8686
8787
Images:
88-
{{ range .Updated.Images -}}
88+
{{ range .Changed.ImageResult.Images -}}
8989
- {{.}} ({{.Policy.Name}})
9090
{{ end -}}
9191
`
@@ -841,6 +841,98 @@ Automation: %s/update-test
841841
}
842842
}
843843

844+
// TestImageUpdateAutomationReconciler_deprecatedTemplateField tests deprecated .Updated template field usage.
845+
// TODO: Remove this test function after .Updated template field is completely removed from the API.
846+
func TestImageUpdateAutomationReconciler_deprecatedTemplateField(t *testing.T) {
847+
g := NewWithT(t)
848+
ctx := context.TODO()
849+
850+
policySpec := imagev1_reflect.ImagePolicySpec{
851+
ImageRepositoryRef: meta.NamespacedObjectReference{
852+
Name: "not-expected-to-exist",
853+
},
854+
Policy: imagev1_reflect.ImagePolicyChoice{
855+
SemVer: &imagev1_reflect.SemVerPolicy{
856+
Range: "1.x",
857+
},
858+
},
859+
}
860+
fixture := "testdata/appconfig"
861+
latest := "helloworld:v1.0.0"
862+
863+
deprecatedTemplate := `Commit summary
864+
865+
Automation: {{ .AutomationObject }}
866+
867+
Files:
868+
{{ range $filename, $_ := .Updated.Files -}}
869+
- {{ $filename }}
870+
{{ end -}}
871+
872+
Objects:
873+
{{ range $resource, $_ := .Updated.Objects -}}
874+
{{ if eq $resource.Kind "Deployment" -}}
875+
- {{ $resource.Kind | lower }} {{ $resource.Name | lower }}
876+
{{ else -}}
877+
- {{ $resource.Kind }} {{ $resource.Name }}
878+
{{ end -}}
879+
{{ end -}}
880+
881+
Images:
882+
{{ range .Updated.Images -}}
883+
- {{.}} ({{.Policy.Name}})
884+
{{ end -}}
885+
`
886+
887+
namespace, err := testEnv.CreateNamespace(ctx, "image-auto-test")
888+
g.Expect(err).ToNot(HaveOccurred())
889+
defer func() { g.Expect(testEnv.Delete(ctx, namespace)).To(Succeed()) }()
890+
891+
testWithRepoAndImagePolicy(
892+
ctx, g, testEnv, namespace.Name, fixture, policySpec, latest,
893+
func(g *WithT, s repoAndPolicyArgs, repoURL string, localRepo *extgogit.Repository) {
894+
policyKey := types.NamespacedName{
895+
Name: s.imagePolicyName,
896+
Namespace: s.namespace,
897+
}
898+
_ = testutil.CommitInRepo(ctx, g, repoURL, s.branch, originRemote, "Install setter marker", func(tmp string) {
899+
g.Expect(testutil.ReplaceMarker(filepath.Join(tmp, "deploy.yaml"), policyKey)).To(Succeed())
900+
})
901+
902+
preChangeCommitId := testutil.CommitIdFromBranch(localRepo, s.branch)
903+
waitForNewHead(g, localRepo, s.branch, preChangeCommitId)
904+
preChangeCommitId = testutil.CommitIdFromBranch(localRepo, s.branch)
905+
906+
updateStrategy := &imagev1.UpdateStrategy{
907+
Strategy: imagev1.UpdateStrategySetters,
908+
}
909+
err := createImageUpdateAutomation(ctx, testEnv, "update-test", s.namespace, s.gitRepoName, s.gitRepoNamespace, s.branch, s.branch, "", deprecatedTemplate, "", updateStrategy)
910+
g.Expect(err).ToNot(HaveOccurred())
911+
defer func() {
912+
g.Expect(deleteImageUpdateAutomation(ctx, testEnv, "update-test", s.namespace)).To(Succeed())
913+
}()
914+
915+
imageUpdateKey := types.NamespacedName{
916+
Namespace: s.namespace,
917+
Name: "update-test",
918+
}
919+
920+
g.Eventually(func() bool {
921+
var imageUpdate imagev1.ImageUpdateAutomation
922+
_ = testEnv.Get(context.TODO(), imageUpdateKey, &imageUpdate)
923+
stalledCondition := apimeta.FindStatusCondition(imageUpdate.Status.Conditions, meta.StalledCondition)
924+
return stalledCondition != nil &&
925+
stalledCondition.Status == metav1.ConditionTrue &&
926+
stalledCondition.Reason == imagev1.DeprecatedTemplateFieldReason &&
927+
strings.Contains(stalledCondition.Message, "template uses deprecated '.Updated' field")
928+
}, timeout).Should(BeTrue())
929+
930+
head, _ := localRepo.Head()
931+
g.Expect(head.Hash().String()).To(Equal(preChangeCommitId))
932+
},
933+
)
934+
}
935+
844936
func TestImageUpdateAutomationReconciler_crossNamespaceRef(t *testing.T) {
845937
g := NewWithT(t)
846938
policySpec := imagev1_reflect.ImagePolicySpec{

internal/source/source.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"os"
2424
"path/filepath"
25+
"regexp"
2526
"strings"
2627
"text/template"
2728
"time"
@@ -48,8 +49,14 @@ import (
4849
// ErrInvalidSourceConfiguration is an error for invalid source configuration.
4950
var ErrInvalidSourceConfiguration = errors.New("invalid source configuration")
5051

52+
// ErrDeprecatedTemplateField is an error for deprecated template field usage.
53+
var ErrDeprecatedTemplateField = errors.New("template uses deprecated '.Updated' field. Please use '.Changed' instead. See: https://fluxcd.io/flux/components/image/imageupdateautomations/#message-template")
54+
5155
const defaultMessageTemplate = `Update from image update automation`
5256

57+
// deprecatedUpdatedPattern matches deprecated .Updated field usage in templates
58+
var deprecatedUpdatedPattern = regexp.MustCompile(`\.Updated\.`)
59+
5360
// TemplateData is the type of the value given to the commit message
5461
// template.
5562
type TemplateData struct {
@@ -346,6 +353,10 @@ func templateMsg(messageTemplate string, templateValues *TemplateData) (string,
346353
messageTemplate = defaultMessageTemplate
347354
}
348355

356+
if deprecatedUpdatedPattern.MatchString(messageTemplate) {
357+
return "", ErrDeprecatedTemplateField
358+
}
359+
349360
// Includes only functions that are guaranteed to always evaluate to the same result for given input.
350361
// This removes the possibility of accidentally relying on where or when the template runs.
351362
// https://github.com/Masterminds/sprig/blob/3ac42c7bc5e4be6aa534e036fb19dde4a996da2e/functions.go#L70

internal/source/source_test.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ import (
5555
)
5656

5757
const (
58-
originRemote = "origin"
59-
testCommitTemplate = `Commit summary
58+
originRemote = "origin"
59+
testCommitTemplateDeprecated = `Commit summary
6060
6161
Automation: {{ .AutomationObject }}
6262
@@ -469,31 +469,20 @@ func test_sourceManager_CommitAndPush(t *testing.T, proto string) {
469469
checkRefSpecBranch string
470470
}{
471471
{
472-
name: "push to cloned branch with custom template",
472+
name: "push to cloned branch with deprecated template field",
473473
gitSpec: &imagev1.GitSpec{
474474
Push: &imagev1.PushSpec{
475475
Branch: "main",
476476
},
477477
Commit: imagev1.CommitSpec{
478-
MessageTemplate: testCommitTemplate,
478+
MessageTemplate: testCommitTemplateDeprecated,
479479
},
480480
},
481481
gitRepoReference: &sourcev1.GitRepositoryRef{
482482
Branch: "main",
483483
},
484484
latestImage: "helloworld:1.0.1",
485-
wantErr: false,
486-
wantCommitMsg: `Commit summary
487-
488-
Automation: test-ns/test-update
489-
490-
Files:
491-
- deploy.yaml
492-
Objects:
493-
- deployment test
494-
Images:
495-
- helloworld:1.0.1 (policy1)
496-
`,
485+
wantErr: true,
497486
},
498487
{
499488
name: "commit with update ResultV2 template",
@@ -771,6 +760,11 @@ Testing: value
771760

772761
pushResult, err := sm.CommitAndPush(ctx, updateAuto, result)
773762
g.Expect(err != nil).To(Equal(tt.wantErr))
763+
if tt.wantErr {
764+
g.Expect(pushResult).To(BeNil())
765+
g.Expect(err).To(MatchError(ErrDeprecatedTemplateField))
766+
return
767+
}
774768
if tt.noChange {
775769
g.Expect(pushResult).To(BeNil())
776770
return

0 commit comments

Comments
 (0)