Skip to content

Commit 0fe6ff7

Browse files
committed
Fix ImagePolicy reconciler checking ImageRepo Ready condition without need
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent 9e8b90f commit 0fe6ff7

File tree

8 files changed

+72
-50
lines changed

8 files changed

+72
-50
lines changed

internal/controller/database.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ limitations under the License.
1717
package controller
1818

1919
// DatabaseWriter implementations record the tags for an image repository.
20+
// It returns the adler32 checksum of the tags set, which can be used to
21+
// determine if the tags have changed since the last time they were
22+
// recorded.
2023
type DatabaseWriter interface {
21-
SetTags(repo string, tags []string) error
24+
SetTags(repo string, tags []string) (string, error)
2225
}
2326

2427
// DatabaseReader implementations get the stored set of tags for an image

internal/controller/imagepolicy_controller.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -330,16 +330,6 @@ func (r *ImagePolicyReconciler) reconcile(ctx context.Context, sp *patch.SerialP
330330
return
331331
}
332332

333-
// Proceed only if the ImageRepository has scan result.
334-
if !conditions.IsReady(repo) {
335-
// Mark not ready but don't requeue. When the repository becomes ready,
336-
// it'll trigger a policy reconciliation. No runtime error to prevent
337-
// requeue.
338-
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.DependencyNotReadyReason, "referenced ImageRepository has not been scanned yet")
339-
result, retErr = ctrl.Result{}, nil
340-
return
341-
}
342-
343333
// Check if the image is valid and mark stalled if not.
344334
if _, err := registry.ParseImageReference(repo.Spec.Image, repo.Spec.Insecure); err != nil {
345335
conditions.MarkStalled(obj, imagev1.ImageURLInvalidReason, "%s", err)
@@ -371,7 +361,7 @@ func (r *ImagePolicyReconciler) reconcile(ctx context.Context, sp *patch.SerialP
371361
// If there's no tag in the database, mark not ready and retry.
372362
if err == errNoTagsInDatabase {
373363
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.DependencyNotReadyReason, "%s", err)
374-
result, retErr = ctrl.Result{}, err
364+
result, retErr = ctrl.Result{}, nil
375365
return
376366
}
377367

internal/controller/imagepolicy_controller_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import (
4747
"github.com/fluxcd/image-reflector-controller/internal/test"
4848
)
4949

50-
func TestImagePolicyReconciler_imageRepoNotReady(t *testing.T) {
50+
func TestImagePolicyReconciler_imageRepoHasNoTags(t *testing.T) {
5151
g := NewWithT(t)
5252

5353
namespaceName := "imagepolicy-" + randStringRunes(5)
@@ -65,19 +65,14 @@ func TestImagePolicyReconciler_imageRepoNotReady(t *testing.T) {
6565
Name: "repo",
6666
},
6767
Spec: imagev1.ImageRepositorySpec{
68-
Image: "ghcr.io/stefanprodan/podinfo/foo:bar:zzz:qqq/aaa",
68+
Image: "ghcr.io/doesnot/exist",
6969
},
7070
}
7171
g.Expect(k8sClient.Create(ctx, imageRepo)).NotTo(HaveOccurred())
7272
t.Cleanup(func() {
7373
g.Expect(k8sClient.Delete(ctx, imageRepo)).NotTo(HaveOccurred())
7474
})
7575

76-
g.Eventually(func() bool {
77-
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(imageRepo), imageRepo)
78-
return err == nil && conditions.IsStalled(imageRepo)
79-
}).Should(BeTrue())
80-
8176
imagePolicy := &imagev1.ImagePolicy{
8277
ObjectMeta: metav1.ObjectMeta{
8378
Namespace: namespaceName,
@@ -87,6 +82,9 @@ func TestImagePolicyReconciler_imageRepoNotReady(t *testing.T) {
8782
ImageRepositoryRef: meta.NamespacedObjectReference{
8883
Name: imageRepo.Name,
8984
},
85+
Policy: imagev1.ImagePolicyChoice{
86+
Alphabetical: &imagev1.AlphabeticalPolicy{},
87+
},
9088
},
9189
}
9290
g.Expect(k8sClient.Create(ctx, imagePolicy)).NotTo(HaveOccurred())
@@ -174,15 +172,17 @@ func TestImagePolicyReconciler_ignoresImageRepoNotReadyEvent(t *testing.T) {
174172
}).Should(BeTrue())
175173

176174
// Check that the ImagePolicy is still ready and does not get updated.
177-
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(imagePolicy), imagePolicy)
178-
g.Expect(err).NotTo(HaveOccurred())
179-
g.Expect(conditions.IsReady(imagePolicy)).To(BeTrue())
175+
g.Eventually(func() bool {
176+
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(imagePolicy), imagePolicy)
177+
return err == nil && conditions.IsReady(imagePolicy)
178+
}).Should(BeTrue())
180179

181180
// Wait a bit and check that the ImagePolicy remains ready.
182181
time.Sleep(time.Second)
183-
err = k8sClient.Get(ctx, client.ObjectKeyFromObject(imagePolicy), imagePolicy)
184-
g.Expect(err).NotTo(HaveOccurred())
185-
g.Expect(conditions.IsReady(imagePolicy)).To(BeTrue())
182+
g.Eventually(func() bool {
183+
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(imagePolicy), imagePolicy)
184+
return err == nil && conditions.IsReady(imagePolicy)
185+
}).Should(BeTrue())
186186
}
187187

188188
func TestImagePolicyReconciler_invalidImage(t *testing.T) {

internal/controller/imagerepository_controller.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"errors"
2222
"fmt"
2323
"regexp"
24+
"slices"
2425
"sort"
26+
"strings"
2527
"time"
2628

2729
"github.com/google/go-containerregistry/pkg/name"
@@ -192,6 +194,7 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
192194
oldObj := obj.DeepCopy()
193195

194196
var foundTags int
197+
var tagsChecksum string
195198
// Store a message about current reconciliation and next scan.
196199
var nextScanMsg string
197200
// Set a default next scan time before processing the object.
@@ -206,7 +209,7 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
206209
return true
207210
}
208211

209-
readyMsg := fmt.Sprintf("successful scan: found %d tags", foundTags)
212+
readyMsg := fmt.Sprintf("successful scan: found %d tags with checksum %s", foundTags, tagsChecksum)
210213
rs := reconcile.NewResultFinalizer(isSuccess, readyMsg)
211214
retErr = rs.Finalize(obj, result, retErr)
212215

@@ -290,25 +293,27 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
290293
// Scan the repository if it's scan time. No scan is a no-op reconciliation.
291294
// The next scan time is not reset in case of no-op reconciliation.
292295
if ok {
293-
reconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "scanning: %s", reasonMsg)
296+
drift := reasonMsg == scanReasonEmptyDatabase
297+
reconcile.ProgressiveStatus(drift, obj, meta.ProgressingReason, "scanning: %s", reasonMsg)
294298
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
295299
result, retErr = ctrl.Result{}, err
296300
return
297301
}
298302

299-
tags, err := r.scan(ctx, obj, ref, opts)
303+
tags, checksum, err := r.scan(ctx, obj, ref, opts)
300304
if err != nil {
301305
e := fmt.Errorf("scan failed: %w", err)
302306
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.ReadOperationFailedReason, "%s", e)
303307
result, retErr = ctrl.Result{}, e
304308
return
305309
}
306310
foundTags = tags
311+
tagsChecksum = checksum
307312

308313
nextScanMsg = fmt.Sprintf("next scan in %s", when.String())
309314
// Check if new tags were found.
310-
if oldObj.Status.LastScanResult != nil &&
311-
oldObj.Status.LastScanResult.TagCount == foundTags {
315+
s := strings.Split(conditions.GetMessage(oldObj, meta.ReadyCondition), " tags with checksum ")
316+
if len(s) == 2 && s[1] == checksum {
312317
nextScanMsg = "no new tags found, " + nextScanMsg
313318
} else {
314319
// When new tags are found, this message will be suppressed by
@@ -405,7 +410,8 @@ func (r *ImageRepositoryReconciler) shouldScan(obj imagev1.ImageRepository, now
405410

406411
// scan performs repository scanning and writes the scanned result in the
407412
// internal database and populates the status of the ImageRepository.
408-
func (r *ImageRepositoryReconciler) scan(ctx context.Context, obj *imagev1.ImageRepository, ref name.Reference, options []remote.Option) (int, error) {
413+
// It returns the number of tags found, the checksum of the tags, and an error.
414+
func (r *ImageRepositoryReconciler) scan(ctx context.Context, obj *imagev1.ImageRepository, ref name.Reference, options []remote.Option) (int, string, error) {
409415
timeout := obj.GetTimeout()
410416
ctx, cancel := context.WithTimeout(ctx, timeout)
411417
defer cancel()
@@ -414,17 +420,20 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, obj *imagev1.Image
414420

415421
tags, err := remote.List(ref.Context(), options...)
416422
if err != nil {
417-
return 0, err
423+
return 0, "", err
418424
}
419425

420426
filteredTags, err := filterOutTags(tags, obj.GetExclusionList())
421427
if err != nil {
422-
return 0, err
428+
return 0, "", err
423429
}
424430

431+
slices.Sort(filteredTags)
432+
425433
canonicalName := ref.Context().String()
426-
if err := r.Database.SetTags(canonicalName, filteredTags); err != nil {
427-
return 0, fmt.Errorf("failed to set tags for %q: %w", canonicalName, err)
434+
checksum, err := r.Database.SetTags(canonicalName, filteredTags)
435+
if err != nil {
436+
return 0, "", fmt.Errorf("failed to set tags for %q: %w", canonicalName, err)
428437
}
429438

430439
scanTime := metav1.Now()
@@ -441,7 +450,7 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, obj *imagev1.Image
441450
obj.Status.SetLastHandledReconcileRequest(token)
442451
}
443452

444-
return len(filteredTags), nil
453+
return len(filteredTags), checksum, nil
445454
}
446455

447456
// reconcileDelete handles the deletion of the object.

internal/controller/imagerepository_controller_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import (
2222
"crypto/x509"
2323
"errors"
2424
"fmt"
25+
"hash/adler32"
2526
"net/http"
2627
"net/url"
28+
"strings"
2729
"testing"
2830
"time"
2931

@@ -56,12 +58,12 @@ type mockDatabase struct {
5658
}
5759

5860
// SetTags implements the DatabaseWriter interface of the Database.
59-
func (db *mockDatabase) SetTags(repo string, tags []string) error {
61+
func (db *mockDatabase) SetTags(repo string, tags []string) (string, error) {
6062
if db.WriteError != nil {
61-
return db.WriteError
63+
return "", db.WriteError
6264
}
6365
db.TagData = append(db.TagData, tags...)
64-
return nil
66+
return fmt.Sprintf("%v", adler32.Checksum([]byte(strings.Join(tags, ",")))), nil
6567
}
6668

6769
// Tags implements the DatabaseReader interface of the Database.
@@ -393,7 +395,7 @@ func TestImageRepositoryReconciler_scan(t *testing.T) {
393395
opts = append(opts, remote.WithTransport(tr))
394396
}
395397

396-
tagCount, err := r.scan(context.TODO(), repo, ref, opts)
398+
tagCount, _, err := r.scan(context.TODO(), repo, ref, opts)
397399
if tt.wantErr != "" {
398400
g.Expect(err).To(HaveOccurred())
399401
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))

internal/database/badger.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package database
1818
import (
1919
"encoding/json"
2020
"fmt"
21+
"hash/adler32"
2122

2223
"github.com/dgraph-io/badger/v3"
2324
)
@@ -54,15 +55,19 @@ func (a *BadgerDatabase) Tags(repo string) ([]string, error) {
5455
// the repo.
5556
//
5657
// It overwrites existing tag sets for the provided repo.
57-
func (a *BadgerDatabase) SetTags(repo string, tags []string) error {
58+
func (a *BadgerDatabase) SetTags(repo string, tags []string) (string, error) {
5859
b, err := marshal(tags)
5960
if err != nil {
60-
return err
61+
return "", err
6162
}
62-
return a.db.Update(func(txn *badger.Txn) error {
63+
err = a.db.Update(func(txn *badger.Txn) error {
6364
e := badger.NewEntry(keyForRepo(tagsPrefix, repo), b)
6465
return txn.SetEntry(e)
6566
})
67+
if err != nil {
68+
return "", err
69+
}
70+
return fmt.Sprintf("%v", adler32.Checksum(b)), nil
6671
}
6772

6873
func keyForRepo(prefix, repo string) []byte {

internal/database/badger_gc_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ func TestBadgerGarbageCollectorDoesStop(t *testing.T) {
4242
time.Sleep(time.Second)
4343

4444
tags := []string{"latest", "v0.0.1", "v0.0.2"}
45-
fatalIfError(t, db.SetTags(testRepo, tags))
46-
_, err := db.Tags(testRepo)
45+
_, err := db.SetTags(testRepo, tags)
46+
fatalIfError(t, err)
47+
_, err = db.Tags(testRepo)
4748
fatalIfError(t, err)
4849
t.Log("wrote tags successfully")
4950

internal/database/badger_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestSetTags(t *testing.T) {
4040
db := createBadgerDatabase(t)
4141
tags := []string{"latest", "v0.0.1", "v0.0.2"}
4242

43-
fatalIfError(t, db.SetTags(testRepo, tags))
43+
fatalIfError(t, setTags(t, db, testRepo, tags, "1943865137"))
4444

4545
loaded, err := db.Tags(testRepo)
4646
fatalIfError(t, err)
@@ -53,9 +53,9 @@ func TestSetTagsOverwrites(t *testing.T) {
5353
db := createBadgerDatabase(t)
5454
tags1 := []string{"latest", "v0.0.1", "v0.0.2"}
5555
tags2 := []string{"latest", "v0.0.1", "v0.0.2", "v0.0.3"}
56-
fatalIfError(t, db.SetTags(testRepo, tags1))
56+
fatalIfError(t, setTags(t, db, testRepo, tags1, "1943865137"))
5757

58-
fatalIfError(t, db.SetTags(testRepo, tags2))
58+
fatalIfError(t, setTags(t, db, testRepo, tags2, "3168012550"))
5959

6060
loaded, err := db.Tags(testRepo)
6161
fatalIfError(t, err)
@@ -67,10 +67,10 @@ func TestSetTagsOverwrites(t *testing.T) {
6767
func TestGetOnlyFetchesForRepo(t *testing.T) {
6868
db := createBadgerDatabase(t)
6969
tags1 := []string{"latest", "v0.0.1", "v0.0.2"}
70-
fatalIfError(t, db.SetTags(testRepo, tags1))
70+
fatalIfError(t, setTags(t, db, testRepo, tags1, "1943865137"))
7171
testRepo2 := "another/repo"
7272
tags2 := []string{"v0.0.3", "v0.0.4"}
73-
fatalIfError(t, db.SetTags(testRepo2, tags2))
73+
fatalIfError(t, setTags(t, db, testRepo2, tags2, "728958008"))
7474

7575
loaded, err := db.Tags(testRepo)
7676
fatalIfError(t, err)
@@ -96,6 +96,18 @@ func createBadgerDatabase(t *testing.T) *BadgerDatabase {
9696
return NewBadgerDatabase(db)
9797
}
9898

99+
func setTags(t *testing.T, db *BadgerDatabase, repo string, tags []string, expectedChecksum string) error {
100+
t.Helper()
101+
checksum, err := db.SetTags(repo, tags)
102+
if err != nil {
103+
t.Fatal(err)
104+
}
105+
if checksum != expectedChecksum {
106+
t.Fatalf("SetTags returned incorrect checksum, want %v, got %v", expectedChecksum, checksum)
107+
}
108+
return nil
109+
}
110+
99111
func fatalIfError(t *testing.T, err error) {
100112
t.Helper()
101113
if err != nil {

0 commit comments

Comments
 (0)