diff --git a/Makefile b/Makefile index aaec6cef..718aacb7 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,11 @@ SOURCE_VER ?= $(shell go list -m github.com/fluxcd/source-controller/api | awk ' # Version of the image-reflector-controller from which to get the ImagePolicy CRD. # Pulls image-reflector-controller/api's version set in go.mod. -REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $$2}') +# If the version has hyphens, it's assumed to be a non-released version +# and the part after the last hyphen is assumed to be a commit hash, +# which is used to fetch the CRD from the repository in this case. +# Otherwise the whole version is used. +REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller/api | awk '{ n = split($$2, a, "-"); print a[n] }') # Repository root based on Git metadata. REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) diff --git a/api/go.mod b/api/go.mod index b579c93b..39253b3b 100644 --- a/api/go.mod +++ b/api/go.mod @@ -18,6 +18,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/text v0.24.0 // indirect diff --git a/api/go.sum b/api/go.sum index b1e5374f..98dc17fe 100644 --- a/api/go.sum +++ b/api/go.sum @@ -40,8 +40,9 @@ github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/api/v1beta2/reference.go b/api/v1beta2/reference.go index 917189ee..c0d9e719 100644 --- a/api/v1beta2/reference.go +++ b/api/v1beta2/reference.go @@ -16,7 +16,9 @@ limitations under the License. package v1beta2 -import "fmt" +import ( + "fmt" +) // CrossNamespaceSourceReference contains enough information to let you locate the // typed Kubernetes resource object at cluster level. @@ -55,10 +57,15 @@ type ImageRef struct { // Tag is the image's tag. // +required Tag string `json:"tag"` + // Digest is the image's digest. + // +optional + Digest string `json:"digest,omitempty"` } -// String combines the components of ImageRef to construct a string -// representation of the image reference. -func (r ImageRef) String() string { - return r.Name + ":" + r.Tag +func (in *ImageRef) String() string { + res := in.Name + ":" + in.Tag + if in.Digest != "" { + res += "@" + in.Digest + } + return res } diff --git a/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml b/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml index 1ff8c40c..2973df70 100644 --- a/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml +++ b/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml @@ -691,6 +691,9 @@ spec: additionalProperties: description: ImageRef represents an image reference. properties: + digest: + description: Digest is the image's digest. + type: string name: description: Name is the bare image's name. type: string diff --git a/docs/api/v1beta2/image-automation.md b/docs/api/v1beta2/image-automation.md index 9ebf3ba9..ab12d64b 100644 --- a/docs/api/v1beta2/image-automation.md +++ b/docs/api/v1beta2/image-automation.md @@ -332,6 +332,18 @@ string

Tag is the image’s tag.

+ + +digest
+ +string + + + +(Optional) +

Digest is the image’s digest.

+ + diff --git a/go.mod b/go.mod index 2dfe314b..7956d319 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/ProtonMail/go-crypto v1.2.0 github.com/cyphar/filepath-securejoin v0.4.1 github.com/fluxcd/image-automation-controller/api v0.40.0 - github.com/fluxcd/image-reflector-controller/api v0.34.0 + github.com/fluxcd/image-reflector-controller/api v0.34.1-0.20250512083550-1c69ffe07af4 github.com/fluxcd/pkg/apis/acl v0.7.0 github.com/fluxcd/pkg/apis/event v0.17.0 github.com/fluxcd/pkg/apis/meta v1.12.0 diff --git a/go.sum b/go.sum index 93efae2d..37088371 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/fluxcd/cli-utils v0.36.0-flux.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6 github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= -github.com/fluxcd/image-reflector-controller/api v0.34.0 h1:+0AGoaYzHYXzVDQO9xq2eGZKkPl81Bfz6xFI7rElBzs= -github.com/fluxcd/image-reflector-controller/api v0.34.0/go.mod h1:C6742RYyZVt2KIyJv16lb4gYbsK+P1RGQeaQ8C8huec= +github.com/fluxcd/image-reflector-controller/api v0.34.1-0.20250512083550-1c69ffe07af4 h1:4wm/cMYP/8bGXvFdxdTvftheXZWDmcnu6XXnif7JG7A= +github.com/fluxcd/image-reflector-controller/api v0.34.1-0.20250512083550-1c69ffe07af4/go.mod h1:kH1hTdo3h08J2ZDnw7w+5D+xcK2DJOY0fwipOG4e8fE= github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0= github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ= github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlbYi5NU7M= diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 00000000..4dc0be0d --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,24 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package constants + +const ( + // SetterShortHand is a shorthand that can be used to mark + // setters; instead of + // # { "$ref": "#/definitions/ + SetterShortHand = "$imagepolicy" +) diff --git a/internal/controller/imageupdateautomation_controller.go b/internal/controller/imageupdateautomation_controller.go index 7132b3c8..c44c8104 100644 --- a/internal/controller/imageupdateautomation_controller.go +++ b/internal/controller/imageupdateautomation_controller.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - "strings" "time" corev1 "k8s.io/api/core/v1" @@ -331,10 +330,14 @@ func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *pat conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } - observedPolicies, err := observedPolicies(policies) - if err != nil { - result, retErr = ctrl.Result{}, err - return + // Index the policies by their name. + observedPolicies := imagev1.ObservedPolicies{} + for _, policy := range policies { + observedPolicies[policy.Name] = imagev1.ImageRef{ + Name: policy.Status.LatestRef.Name, + Tag: policy.Status.LatestRef.Tag, + Digest: policy.Status.LatestRef.Digest, + } } // If the policies have changed, require a full sync. @@ -547,7 +550,7 @@ func getPolicies(ctx context.Context, kclient client.Client, namespace string, s readyPolicies := []imagev1_reflect.ImagePolicy{} for _, policy := range policies.Items { // Ignore the policies that don't have a latest image. - if policy.Status.LatestImage == "" { + if policy.Status.LatestRef == nil { continue } readyPolicies = append(readyPolicies, policy) @@ -556,31 +559,6 @@ func getPolicies(ctx context.Context, kclient client.Client, namespace string, s return readyPolicies, nil } -// observedPolicies takes a list of ImagePolicies and returns an -// ObservedPolicies with all the policies in it. -func observedPolicies(policies []imagev1_reflect.ImagePolicy) (imagev1.ObservedPolicies, error) { - observedPolicies := imagev1.ObservedPolicies{} - for _, policy := range policies { - name, tag := splitByLastColon(policy.Status.LatestImage) - if name == "" || tag == "" { - return nil, fmt.Errorf("failed parsing image: %s", policy.Status.LatestImage) - } - observedPolicies[policy.Name] = imagev1.ImageRef{ - Name: name, - Tag: tag, - } - } - return observedPolicies, nil -} - -func splitByLastColon(latestImage string) (string, string) { - idx := strings.LastIndex(latestImage, ":") - if idx == -1 { - return latestImage, "" - } - return latestImage[:idx], latestImage[idx+1:] -} - // observedPoliciesChanged returns if the previous and current observedPolicies // have changed. func observedPoliciesChanged(previous, current imagev1.ObservedPolicies) bool { diff --git a/internal/controller/imageupdateautomation_controller_test.go b/internal/controller/imageupdateautomation_controller_test.go index 52d7d296..f5705908 100644 --- a/internal/controller/imageupdateautomation_controller_test.go +++ b/internal/controller/imageupdateautomation_controller_test.go @@ -1532,7 +1532,7 @@ func Test_getPolicies(t *testing.T) { aPolicy.Name = p.name aPolicy.Namespace = p.namespace aPolicy.Status = imagev1_reflect.ImagePolicyStatus{ - LatestImage: p.latestImage, + LatestRef: testutil.ImageToRef(p.latestImage), } aPolicy.Labels = p.labels testObjects = append(testObjects, aPolicy) @@ -1555,65 +1555,6 @@ func Test_getPolicies(t *testing.T) { } } -func Test_observedPolicies(t *testing.T) { - tests := []struct { - name string - policyWithImage map[string]string - want imagev1.ObservedPolicies - wantErr bool - }{ - { - name: "good policies", - policyWithImage: map[string]string{ - "p1": "aaa:bbb", - "p2": "ccc:ddd", - "p3": "eee:latest", - "p4": "registry.localhost:5000/sample-web:0.1.0", - }, - want: imagev1.ObservedPolicies{ - "p1": imagev1.ImageRef{Name: "aaa", Tag: "bbb"}, - "p2": imagev1.ImageRef{Name: "ccc", Tag: "ddd"}, - "p3": imagev1.ImageRef{Name: "eee", Tag: "latest"}, - "p4": imagev1.ImageRef{Name: "registry.localhost:5000/sample-web", Tag: "0.1.0"}, - }, - }, - { - name: "bad policy image with no tag", - policyWithImage: map[string]string{ - "p1": "aaa", - }, - wantErr: true, - }, - { - name: "no policy", - want: imagev1.ObservedPolicies{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) - - policies := []imagev1_reflect.ImagePolicy{} - for name, image := range tt.policyWithImage { - aPolicy := imagev1_reflect.ImagePolicy{} - aPolicy.Name = name - aPolicy.Status = imagev1_reflect.ImagePolicyStatus{ - LatestImage: image, - } - policies = append(policies, aPolicy) - } - - result, err := observedPolicies(policies) - if (err != nil) != tt.wantErr { - g.Fail(fmt.Sprintf("unexpected error: %v", err)) - } - if err == nil { - g.Expect(result).To(Equal(tt.want)) - } - }) - } -} - func Test_observedPoliciesChanged(t *testing.T) { tests := []struct { name string @@ -1902,7 +1843,7 @@ func createImagePolicyWithLatestImageForSpec(ctx context.Context, kClient client return err } patch := client.MergeFrom(policy.DeepCopy()) - policy.Status.LatestImage = latest + policy.Status.LatestRef = testutil.ImageToRef(latest) return kClient.Status().Patch(ctx, policy, patch) } @@ -1916,7 +1857,7 @@ func updateImagePolicyWithLatestImage(ctx context.Context, kClient client.Client return err } patch := client.MergeFrom(policy.DeepCopy()) - policy.Status.LatestImage = latest + policy.Status.LatestRef = testutil.ImageToRef(latest) return kClient.Status().Patch(ctx, policy, patch) } diff --git a/internal/controller/predicate.go b/internal/controller/predicate.go index 03893d3b..380128bf 100644 --- a/internal/controller/predicate.go +++ b/internal/controller/predicate.go @@ -53,7 +53,11 @@ func (latestImageChangePredicate) Update(e event.UpdateEvent) bool { return false } - if oldSource.Status.LatestImage != newSource.Status.LatestImage { + if newSource.Status.LatestRef == nil { + return false + } + + if oldSource.Status.LatestRef == nil || *oldSource.Status.LatestRef != *newSource.Status.LatestRef { return true } diff --git a/internal/controller/predicate_test.go b/internal/controller/predicate_test.go index e6479b25..ae5819dc 100644 --- a/internal/controller/predicate_test.go +++ b/internal/controller/predicate_test.go @@ -35,24 +35,24 @@ func Test_latestImageChangePredicate_Update(t *testing.T) { { name: "no latest image", beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) { - oldObj.Status.LatestImage = "" - newObj.Status.LatestImage = "" + oldObj.Status.LatestRef = nil + newObj.Status.LatestRef = nil }, want: false, }, { name: "new image, no old image", beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) { - oldObj.Status.LatestImage = "" - newObj.Status.LatestImage = "foo" + oldObj.Status.LatestRef = nil + newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"} }, want: true, }, { name: "different old and new image", beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) { - oldObj.Status.LatestImage = "bar" - newObj.Status.LatestImage = "foo" + oldObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "bar"} + newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"} }, want: true, }, diff --git a/internal/policy/applier_test.go b/internal/policy/applier_test.go index e183c483..7fc1bf59 100644 --- a/internal/policy/applier_test.go +++ b/internal/policy/applier_test.go @@ -124,7 +124,7 @@ func Test_applyPolicies(t *testing.T) { policy.Name = name policy.Namespace = testNS policy.Status = imagev1_reflect.ImagePolicyStatus{ - LatestImage: image, + LatestRef: testutil.ImageToRef(image), } policyList = append(policyList, *policy) } diff --git a/internal/source/source_test.go b/internal/source/source_test.go index 00c6e575..a62a9b21 100644 --- a/internal/source/source_test.go +++ b/internal/source/source_test.go @@ -640,7 +640,7 @@ Testing: value imgPolicy.Name = "policy1" imgPolicy.Namespace = testNS imgPolicy.Status = imagev1_reflect.ImagePolicyStatus{ - LatestImage: tt.latestImage, + LatestRef: testutil.ImageToRef(tt.latestImage), } testObjects = append(testObjects, imgPolicy) policyKey := client.ObjectKeyFromObject(imgPolicy) @@ -865,7 +865,7 @@ func test_pushBranchUpdateScenarios(t *testing.T, proto string, srcOpts []Source imgPolicy.Name = "policy1" imgPolicy.Namespace = testNS imgPolicy.Status = imagev1_reflect.ImagePolicyStatus{ - LatestImage: latestImage, + LatestRef: testutil.ImageToRef(latestImage), } testObjects = append(testObjects, imgPolicy) // Take the policyKey to update the setter marker with. @@ -966,7 +966,7 @@ func test_pushBranchUpdateScenarios(t *testing.T, proto string, srcOpts []Source // Update latest image. latestImage = "helloworld:v1.3.0" - imgPolicy.Status.LatestImage = latestImage + imgPolicy.Status.LatestRef = testutil.ImageToRef(latestImage) g.Expect(kClient.Update(ctx, imgPolicy)).To(Succeed()) policies = []imagev1_reflect.ImagePolicy{*imgPolicy} @@ -1014,7 +1014,7 @@ func test_pushBranchUpdateScenarios(t *testing.T, proto string, srcOpts []Source // Update latest image. latestImage = "helloworld:v1.3.1" - imgPolicy.Status.LatestImage = latestImage + imgPolicy.Status.LatestRef = testutil.ImageToRef(latestImage) g.Expect(kClient.Update(ctx, imgPolicy)).To(Succeed()) policies = []imagev1_reflect.ImagePolicy{*imgPolicy} diff --git a/internal/testutil/util.go b/internal/testutil/util.go index 53324df6..dc6baba1 100644 --- a/internal/testutil/util.go +++ b/internal/testutil/util.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "time" "github.com/ProtonMail/go-crypto/openpgp" @@ -43,7 +44,8 @@ import ( "github.com/fluxcd/pkg/gittestserver" - "github.com/fluxcd/image-automation-controller/pkg/update" + "github.com/fluxcd/image-automation-controller/internal/constants" + imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2" ) const ( @@ -80,7 +82,7 @@ func ReplaceMarkerWithMarker(path string, policyKey types.NamespacedName, marker } func setterRef(name types.NamespacedName) string { - return fmt.Sprintf(`{"%s": "%s:%s"}`, update.SetterShortHand, name.Namespace, name.Name) + return fmt.Sprintf(`{"%s": "%s:%s"}`, constants.SetterShortHand, name.Namespace, name.Name) } func CommitInRepo(ctx context.Context, g *WithT, repoURL, branch, remote, msg string, changeFiles func(path string)) plumbing.Hash { @@ -456,3 +458,23 @@ func GetSigningKeyPair(g *WithT, passphrase string) (*openpgp.Entity, []byte) { return pgpEntity, b.Bytes() } + +func ImageToRef(image string) *imagev1_reflect.ImageRef { + var digest string + + if idx := strings.LastIndex(image, "@"); idx != -1 { + image, digest = image[:idx], image[idx+1:] + } + + var tag string + + if idx := strings.LastIndex(image, ":"); idx != -1 { + image, tag = image[:idx], image[idx+1:] + } + + return &imagev1_reflect.ImageRef{ + Name: image, + Tag: tag, + Digest: digest, + } +} diff --git a/pkg/update/setters.go b/pkg/update/setters.go index d8123c92..c5566fde 100644 --- a/pkg/update/setters.go +++ b/pkg/update/setters.go @@ -18,7 +18,6 @@ package update import ( "fmt" - "strings" "github.com/go-logr/logr" "github.com/google/go-containerregistry/pkg/name" @@ -31,21 +30,17 @@ import ( "sigs.k8s.io/kustomize/kyaml/sets" "sigs.k8s.io/kustomize/kyaml/yaml" + "github.com/fluxcd/image-automation-controller/internal/constants" imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2" ) const ( // This is preserved from setters2 K8sCliExtensionKey = "x-k8s-cli" - - // SetterShortHand is a shorthand that can be used to mark - // setters; instead of - // # { "$ref": "#/definitions/ - SetterShortHand = "$imagepolicy" ) func init() { - fieldmeta.SetShortHandRef(SetterShortHand) + fieldmeta.SetShortHandRef(constants.SetterShortHand) // this prevents the global schema, should it be initialised, from // parsing all the Kubernetes openAPI definitions, which is not // necessary. @@ -144,7 +139,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies } result.Files[file] = fileres } - objres, ok := fileres.Objects[oid] + objres := fileres.Objects[oid] for _, n := range objres { if n == ref { return @@ -156,7 +151,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies defs := map[string]spec.Schema{} for _, policy := range policies { - if policy.Status.LatestImage == "" { + if policy.Status.LatestRef == nil { continue } // Using strict validation would mean any image that omits the @@ -165,10 +160,10 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies // filled in. Usually this would mean the tag would end up // being `latest` if empty in the input; but I'm assuming here // that the policy won't have a tagless ref. - image := policy.Status.LatestImage + image := policy.Status.LatestRef.String() r, err := name.ParseReference(image, name.WeakValidation) if err != nil { - return ResultV2{}, fmt.Errorf("encountered invalid image ref %q: %w", policy.Status.LatestImage, err) + return ResultV2{}, fmt.Errorf("encountered invalid image ref %q: %w", image, err) } ref := imageRef{ Reference: r, @@ -178,15 +173,13 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies }, } - tag := ref.Identifier() - // annoyingly, neither the library imported above, nor an - // alternative I found, will yield the original image name; - // this is an easy way to get it - name := strings.TrimSuffix(image, ":"+tag) + tag := policy.Status.LatestRef.Tag + name := policy.Status.LatestRef.Name + digest := policy.Status.LatestRef.Digest imageSetter := fmt.Sprintf("%s:%s", policy.GetNamespace(), policy.GetName()) tracelog.Info("adding setter", "name", imageSetter) - defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, policy.Status.LatestImage) + defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, image) imageRefs[imageSetter] = ref tagSetter := imageSetter + ":tag" @@ -194,11 +187,15 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies defs[fieldmeta.SetterDefinitionPrefix+tagSetter] = setterSchema(tagSetter, tag) imageRefs[tagSetter] = ref - // Context().Name() gives the image repository _as supplied_ nameSetter := imageSetter + ":name" tracelog.Info("adding setter", "name", nameSetter) defs[fieldmeta.SetterDefinitionPrefix+nameSetter] = setterSchema(nameSetter, name) imageRefs[nameSetter] = ref + + digestSetter := imageSetter + ":digest" + tracelog.Info("adding setter", "name", digestSetter) + defs[fieldmeta.SetterDefinitionPrefix+digestSetter] = setterSchema(digestSetter, digest) + imageRefs[digestSetter] = ref } settersSchema.Definitions = defs @@ -206,7 +203,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies // get ready with the reader and writer reader := &ScreeningLocalReader{ Path: inpath, - Token: fmt.Sprintf("%q", SetterShortHand), + Token: fmt.Sprintf("%q", constants.SetterShortHand), Trace: tracelog, } writer := &kio.LocalPackageWriter{ diff --git a/pkg/update/testdata/setters/expected/kustomization.yml b/pkg/update/testdata/setters/expected/kustomization.yml index 739a25dd..cf492cb6 100644 --- a/pkg/update/testdata/setters/expected/kustomization.yml +++ b/pkg/update/testdata/setters/expected/kustomization.yml @@ -7,6 +7,7 @@ images: - name: container newName: index.repo.fake/updated # {"$imagepolicy": "automation-ns:policy:name"} newTag: v1.0.1 # {"$imagepolicy": "automation-ns:policy:tag"} + newDigest: sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be # {"$imagepolicy": "automation-ns:digest:digest"} # Prove fix for https://github.com/fluxcd/flux2/issues/3284 patches: - patch: | @@ -24,3 +25,4 @@ patches: version: v1 kind: Deployment name: sxxxxdadminservice + image: image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be # {"$imagepolicy": "automation-ns:digest"} diff --git a/pkg/update/testdata/setters/original/kustomization.yml b/pkg/update/testdata/setters/original/kustomization.yml index d1cb6a22..9a7d5eaf 100644 --- a/pkg/update/testdata/setters/original/kustomization.yml +++ b/pkg/update/testdata/setters/original/kustomization.yml @@ -7,6 +7,7 @@ images: - name: container newName: replaced # {"$imagepolicy": "automation-ns:policy:name"} newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"} + newDigest: sha256:1234567890abcdef # {"$imagepolicy": "automation-ns:digest:digest"} # Prove fix for https://github.com/fluxcd/flux2/issues/3284 patches: - patch: | @@ -24,3 +25,4 @@ patches: version: v1 kind: Deployment name: sxxxxdadminservice + image: image # {"$imagepolicy": "automation-ns:digest"} diff --git a/pkg/update/update_test.go b/pkg/update/update_test.go index 36f428b2..c28d783f 100644 --- a/pkg/update/update_test.go +++ b/pkg/update/update_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/kustomize/kyaml/yaml" + "github.com/fluxcd/image-automation-controller/internal/testutil" "github.com/fluxcd/image-automation-controller/pkg/test" imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2" ) @@ -35,21 +36,30 @@ func TestUpdateWithSetters(t *testing.T) { policies := []imagev1_reflect.ImagePolicy{ { - ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected} + ObjectMeta: metav1.ObjectMeta{ Namespace: "automation-ns", Name: "policy", }, Status: imagev1_reflect.ImagePolicyStatus{ - LatestImage: "index.repo.fake/updated:v1.0.1", + LatestRef: testutil.ImageToRef("index.repo.fake/updated:v1.0.1"), }, }, { - ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected} + ObjectMeta: metav1.ObjectMeta{ Namespace: "automation-ns", Name: "unchanged", }, Status: imagev1_reflect.ImagePolicyStatus{ - LatestImage: "image:v1.0.0", + LatestRef: testutil.ImageToRef("image:v1.0.0"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "automation-ns", + Name: "digest", + }, + Status: imagev1_reflect.ImagePolicyStatus{ + LatestRef: testutil.ImageToRef("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"), }, }, } @@ -75,18 +85,26 @@ func TestUpdateWithSetters(t *testing.T) { Name: "foo", }, }} + r, _ := name.ParseReference("index.repo.fake/updated:v1.0.1") expectedImageRef := imageRef{r, types.NamespacedName{ Name: "policy", Namespace: "automation-ns", }} + r, _ = name.ParseReference("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be") + expectedImageRefDigest := imageRef{r, types.NamespacedName{ + Name: "digest", + Namespace: "automation-ns", + }} + expectedResult := Result{ Files: map[string]FileResult{ "kustomization.yml": { Objects: map[ObjectIdentifier][]ImageRef{ kustomizeResourceID: { expectedImageRef, + expectedImageRefDigest, }, }, }, @@ -130,6 +148,16 @@ func TestUpdateWithSetters(t *testing.T) { NewValue: "v1.0.1", Setter: "automation-ns:policy:tag", }, + { + OldValue: "sha256:1234567890abcdef", + NewValue: "sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be", + Setter: "automation-ns:digest:digest", + }, + { + OldValue: "image", + NewValue: "image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be", + Setter: "automation-ns:digest", + }, }, }, "Kustomization": { diff --git a/tests/fuzz/oss_fuzz_prebuild.sh b/tests/fuzz/oss_fuzz_prebuild.sh index 3c971873..daf343ee 100755 --- a/tests/fuzz/oss_fuzz_prebuild.sh +++ b/tests/fuzz/oss_fuzz_prebuild.sh @@ -30,7 +30,11 @@ SOURCE_VER=$(go list -m github.com/fluxcd/source-controller/api | awk '{print $2 # Version of the image-reflector-controller from which to get the ImagePolicy CRD. # Pulls image-reflector-controller/api's version set in go.mod. -REFLECTOR_VER=$(go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $2}') +# If the version has hyphens, it's assumed to be a non-released version +# and the part after the last hyphen is assumed to be a commit hash, +# which is used to fetch the CRD from the repository in this case. +# Otherwise the whole version is used. +REFLECTOR_VER=$(go list -m github.com/fluxcd/image-reflector-controller/api | awk '{ n = split($2, a, "-"); print a[n] }') if [ -d "../../internal/controller/testdata/crds" ]; then cp ../../internal/controller/testdata/crds/*.yaml testdata/crds