Skip to content

Commit fe06222

Browse files
committed
Update digest of latest image
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent 6c5ffee commit fe06222

21 files changed

+171
-141
lines changed

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ SOURCE_VER ?= $(shell go list -m github.com/fluxcd/source-controller/api | awk '
2727

2828
# Version of the image-reflector-controller from which to get the ImagePolicy CRD.
2929
# Pulls image-reflector-controller/api's version set in go.mod.
30-
REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $$2}')
30+
# If the version has hyphens, it's assumed to be a non-released version
31+
# and the part after the last hyphen is assumed to be a commit hash,
32+
# which is used to fetch the CRD from the repository in this case.
33+
# Otherwise the whole version is used.
34+
REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller/api | awk '{ n = split($$2, a, "-"); print a[n] }')
3135

3236
# Repository root based on Git metadata.
3337
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)

api/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
github.com/kr/text v0.2.0 // indirect
1919
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2020
github.com/modern-go/reflect2 v1.0.2 // indirect
21+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
2122
github.com/x448/float16 v0.8.4 // indirect
2223
golang.org/x/net v0.39.0 // indirect
2324
golang.org/x/text v0.24.0 // indirect

api/go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg
4040
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
4141
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
4242
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
43-
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4443
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
44+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
45+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4546
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
4647
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
4748
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

api/v1beta2/reference.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ limitations under the License.
1616

1717
package v1beta2
1818

19-
import "fmt"
19+
import (
20+
"fmt"
21+
)
2022

2123
// CrossNamespaceSourceReference contains enough information to let you locate the
2224
// typed Kubernetes resource object at cluster level.
@@ -55,10 +57,15 @@ type ImageRef struct {
5557
// Tag is the image's tag.
5658
// +required
5759
Tag string `json:"tag"`
60+
// Digest is the image's digest.
61+
// +optional
62+
Digest string `json:"digest,omitempty"`
5863
}
5964

60-
// String combines the components of ImageRef to construct a string
61-
// representation of the image reference.
62-
func (r ImageRef) String() string {
63-
return r.Name + ":" + r.Tag
65+
func (in *ImageRef) String() string {
66+
res := in.Name + ":" + in.Tag
67+
if in.Digest != "" {
68+
res += "@" + in.Digest
69+
}
70+
return res
6471
}

config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,9 @@ spec:
691691
additionalProperties:
692692
description: ImageRef represents an image reference.
693693
properties:
694+
digest:
695+
description: Digest is the image's digest.
696+
type: string
694697
name:
695698
description: Name is the bare image's name.
696699
type: string

docs/api/v1beta2/image-automation.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,18 @@ string
332332
<p>Tag is the image&rsquo;s tag.</p>
333333
</td>
334334
</tr>
335+
<tr>
336+
<td>
337+
<code>digest</code><br>
338+
<em>
339+
string
340+
</em>
341+
</td>
342+
<td>
343+
<em>(Optional)</em>
344+
<p>Digest is the image&rsquo;s digest.</p>
345+
</td>
346+
</tr>
335347
</tbody>
336348
</table>
337349
</div>

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require (
1616
github.com/ProtonMail/go-crypto v1.2.0
1717
github.com/cyphar/filepath-securejoin v0.4.1
1818
github.com/fluxcd/image-automation-controller/api v0.40.0
19-
github.com/fluxcd/image-reflector-controller/api v0.34.0
19+
github.com/fluxcd/image-reflector-controller/api v0.34.1-0.20250512083550-1c69ffe07af4
2020
github.com/fluxcd/pkg/apis/acl v0.7.0
2121
github.com/fluxcd/pkg/apis/event v0.17.0
2222
github.com/fluxcd/pkg/apis/meta v1.12.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ github.com/fluxcd/cli-utils v0.36.0-flux.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6
110110
github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA=
111111
github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
112112
github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=
113-
github.com/fluxcd/image-reflector-controller/api v0.34.0 h1:+0AGoaYzHYXzVDQO9xq2eGZKkPl81Bfz6xFI7rElBzs=
114-
github.com/fluxcd/image-reflector-controller/api v0.34.0/go.mod h1:C6742RYyZVt2KIyJv16lb4gYbsK+P1RGQeaQ8C8huec=
113+
github.com/fluxcd/image-reflector-controller/api v0.34.1-0.20250512083550-1c69ffe07af4 h1:4wm/cMYP/8bGXvFdxdTvftheXZWDmcnu6XXnif7JG7A=
114+
github.com/fluxcd/image-reflector-controller/api v0.34.1-0.20250512083550-1c69ffe07af4/go.mod h1:kH1hTdo3h08J2ZDnw7w+5D+xcK2DJOY0fwipOG4e8fE=
115115
github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0=
116116
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
117117
github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlbYi5NU7M=

internal/constants/constants.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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 constants
18+
19+
const (
20+
// SetterShortHand is a shorthand that can be used to mark
21+
// setters; instead of
22+
// # { "$ref": "#/definitions/
23+
SetterShortHand = "$imagepolicy"
24+
)

internal/controller/imageupdateautomation_controller.go

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23-
"strings"
2423
"time"
2524

2625
corev1 "k8s.io/api/core/v1"
@@ -331,10 +330,14 @@ func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *pat
331330
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
332331
}
333332

334-
observedPolicies, err := observedPolicies(policies)
335-
if err != nil {
336-
result, retErr = ctrl.Result{}, err
337-
return
333+
// Index the policies by their name.
334+
observedPolicies := imagev1.ObservedPolicies{}
335+
for _, policy := range policies {
336+
observedPolicies[policy.Name] = imagev1.ImageRef{
337+
Name: policy.Status.LatestRef.Name,
338+
Tag: policy.Status.LatestRef.Tag,
339+
Digest: policy.Status.LatestRef.Digest,
340+
}
338341
}
339342

340343
// If the policies have changed, require a full sync.
@@ -547,7 +550,7 @@ func getPolicies(ctx context.Context, kclient client.Client, namespace string, s
547550
readyPolicies := []imagev1_reflect.ImagePolicy{}
548551
for _, policy := range policies.Items {
549552
// Ignore the policies that don't have a latest image.
550-
if policy.Status.LatestImage == "" {
553+
if policy.Status.LatestRef == nil {
551554
continue
552555
}
553556
readyPolicies = append(readyPolicies, policy)
@@ -556,31 +559,6 @@ func getPolicies(ctx context.Context, kclient client.Client, namespace string, s
556559
return readyPolicies, nil
557560
}
558561

559-
// observedPolicies takes a list of ImagePolicies and returns an
560-
// ObservedPolicies with all the policies in it.
561-
func observedPolicies(policies []imagev1_reflect.ImagePolicy) (imagev1.ObservedPolicies, error) {
562-
observedPolicies := imagev1.ObservedPolicies{}
563-
for _, policy := range policies {
564-
name, tag := splitByLastColon(policy.Status.LatestImage)
565-
if name == "" || tag == "" {
566-
return nil, fmt.Errorf("failed parsing image: %s", policy.Status.LatestImage)
567-
}
568-
observedPolicies[policy.Name] = imagev1.ImageRef{
569-
Name: name,
570-
Tag: tag,
571-
}
572-
}
573-
return observedPolicies, nil
574-
}
575-
576-
func splitByLastColon(latestImage string) (string, string) {
577-
idx := strings.LastIndex(latestImage, ":")
578-
if idx == -1 {
579-
return latestImage, ""
580-
}
581-
return latestImage[:idx], latestImage[idx+1:]
582-
}
583-
584562
// observedPoliciesChanged returns if the previous and current observedPolicies
585563
// have changed.
586564
func observedPoliciesChanged(previous, current imagev1.ObservedPolicies) bool {

internal/controller/imageupdateautomation_controller_test.go

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,7 +1532,7 @@ func Test_getPolicies(t *testing.T) {
15321532
aPolicy.Name = p.name
15331533
aPolicy.Namespace = p.namespace
15341534
aPolicy.Status = imagev1_reflect.ImagePolicyStatus{
1535-
LatestImage: p.latestImage,
1535+
LatestRef: testutil.ImageToRef(p.latestImage),
15361536
}
15371537
aPolicy.Labels = p.labels
15381538
testObjects = append(testObjects, aPolicy)
@@ -1555,65 +1555,6 @@ func Test_getPolicies(t *testing.T) {
15551555
}
15561556
}
15571557

1558-
func Test_observedPolicies(t *testing.T) {
1559-
tests := []struct {
1560-
name string
1561-
policyWithImage map[string]string
1562-
want imagev1.ObservedPolicies
1563-
wantErr bool
1564-
}{
1565-
{
1566-
name: "good policies",
1567-
policyWithImage: map[string]string{
1568-
"p1": "aaa:bbb",
1569-
"p2": "ccc:ddd",
1570-
"p3": "eee:latest",
1571-
"p4": "registry.localhost:5000/sample-web:0.1.0",
1572-
},
1573-
want: imagev1.ObservedPolicies{
1574-
"p1": imagev1.ImageRef{Name: "aaa", Tag: "bbb"},
1575-
"p2": imagev1.ImageRef{Name: "ccc", Tag: "ddd"},
1576-
"p3": imagev1.ImageRef{Name: "eee", Tag: "latest"},
1577-
"p4": imagev1.ImageRef{Name: "registry.localhost:5000/sample-web", Tag: "0.1.0"},
1578-
},
1579-
},
1580-
{
1581-
name: "bad policy image with no tag",
1582-
policyWithImage: map[string]string{
1583-
"p1": "aaa",
1584-
},
1585-
wantErr: true,
1586-
},
1587-
{
1588-
name: "no policy",
1589-
want: imagev1.ObservedPolicies{},
1590-
},
1591-
}
1592-
for _, tt := range tests {
1593-
t.Run(tt.name, func(t *testing.T) {
1594-
g := NewWithT(t)
1595-
1596-
policies := []imagev1_reflect.ImagePolicy{}
1597-
for name, image := range tt.policyWithImage {
1598-
aPolicy := imagev1_reflect.ImagePolicy{}
1599-
aPolicy.Name = name
1600-
aPolicy.Status = imagev1_reflect.ImagePolicyStatus{
1601-
LatestImage: image,
1602-
}
1603-
policies = append(policies, aPolicy)
1604-
}
1605-
1606-
result, err := observedPolicies(policies)
1607-
if (err != nil) != tt.wantErr {
1608-
g.Fail(fmt.Sprintf("unexpected error: %v", err))
1609-
}
1610-
if err == nil {
1611-
g.Expect(result).To(Equal(tt.want))
1612-
}
1613-
})
1614-
}
1615-
}
1616-
16171558
func Test_observedPoliciesChanged(t *testing.T) {
16181559
tests := []struct {
16191560
name string
@@ -1902,7 +1843,7 @@ func createImagePolicyWithLatestImageForSpec(ctx context.Context, kClient client
19021843
return err
19031844
}
19041845
patch := client.MergeFrom(policy.DeepCopy())
1905-
policy.Status.LatestImage = latest
1846+
policy.Status.LatestRef = testutil.ImageToRef(latest)
19061847
return kClient.Status().Patch(ctx, policy, patch)
19071848
}
19081849

@@ -1916,7 +1857,7 @@ func updateImagePolicyWithLatestImage(ctx context.Context, kClient client.Client
19161857
return err
19171858
}
19181859
patch := client.MergeFrom(policy.DeepCopy())
1919-
policy.Status.LatestImage = latest
1860+
policy.Status.LatestRef = testutil.ImageToRef(latest)
19201861
return kClient.Status().Patch(ctx, policy, patch)
19211862
}
19221863

internal/controller/predicate.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ func (latestImageChangePredicate) Update(e event.UpdateEvent) bool {
5353
return false
5454
}
5555

56-
if oldSource.Status.LatestImage != newSource.Status.LatestImage {
56+
if newSource.Status.LatestRef == nil {
57+
return false
58+
}
59+
60+
if oldSource.Status.LatestRef == nil || *oldSource.Status.LatestRef != *newSource.Status.LatestRef {
5761
return true
5862
}
5963

internal/controller/predicate_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,24 @@ func Test_latestImageChangePredicate_Update(t *testing.T) {
3535
{
3636
name: "no latest image",
3737
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
38-
oldObj.Status.LatestImage = ""
39-
newObj.Status.LatestImage = ""
38+
oldObj.Status.LatestRef = nil
39+
newObj.Status.LatestRef = nil
4040
},
4141
want: false,
4242
},
4343
{
4444
name: "new image, no old image",
4545
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
46-
oldObj.Status.LatestImage = ""
47-
newObj.Status.LatestImage = "foo"
46+
oldObj.Status.LatestRef = nil
47+
newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"}
4848
},
4949
want: true,
5050
},
5151
{
5252
name: "different old and new image",
5353
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
54-
oldObj.Status.LatestImage = "bar"
55-
newObj.Status.LatestImage = "foo"
54+
oldObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "bar"}
55+
newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"}
5656
},
5757
want: true,
5858
},

internal/policy/applier_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func Test_applyPolicies(t *testing.T) {
124124
policy.Name = name
125125
policy.Namespace = testNS
126126
policy.Status = imagev1_reflect.ImagePolicyStatus{
127-
LatestImage: image,
127+
LatestRef: testutil.ImageToRef(image),
128128
}
129129
policyList = append(policyList, *policy)
130130
}

internal/source/source_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ Testing: value
640640
imgPolicy.Name = "policy1"
641641
imgPolicy.Namespace = testNS
642642
imgPolicy.Status = imagev1_reflect.ImagePolicyStatus{
643-
LatestImage: tt.latestImage,
643+
LatestRef: testutil.ImageToRef(tt.latestImage),
644644
}
645645
testObjects = append(testObjects, imgPolicy)
646646
policyKey := client.ObjectKeyFromObject(imgPolicy)
@@ -865,7 +865,7 @@ func test_pushBranchUpdateScenarios(t *testing.T, proto string, srcOpts []Source
865865
imgPolicy.Name = "policy1"
866866
imgPolicy.Namespace = testNS
867867
imgPolicy.Status = imagev1_reflect.ImagePolicyStatus{
868-
LatestImage: latestImage,
868+
LatestRef: testutil.ImageToRef(latestImage),
869869
}
870870
testObjects = append(testObjects, imgPolicy)
871871
// Take the policyKey to update the setter marker with.
@@ -966,7 +966,7 @@ func test_pushBranchUpdateScenarios(t *testing.T, proto string, srcOpts []Source
966966

967967
// Update latest image.
968968
latestImage = "helloworld:v1.3.0"
969-
imgPolicy.Status.LatestImage = latestImage
969+
imgPolicy.Status.LatestRef = testutil.ImageToRef(latestImage)
970970
g.Expect(kClient.Update(ctx, imgPolicy)).To(Succeed())
971971

972972
policies = []imagev1_reflect.ImagePolicy{*imgPolicy}
@@ -1014,7 +1014,7 @@ func test_pushBranchUpdateScenarios(t *testing.T, proto string, srcOpts []Source
10141014

10151015
// Update latest image.
10161016
latestImage = "helloworld:v1.3.1"
1017-
imgPolicy.Status.LatestImage = latestImage
1017+
imgPolicy.Status.LatestRef = testutil.ImageToRef(latestImage)
10181018
g.Expect(kClient.Update(ctx, imgPolicy)).To(Succeed())
10191019

10201020
policies = []imagev1_reflect.ImagePolicy{*imgPolicy}

0 commit comments

Comments
 (0)