@@ -18,17 +18,14 @@ package controllers
18
18
19
19
import (
20
20
"context"
21
- "crypto/sha256"
22
21
"errors"
23
22
"fmt"
24
23
"os"
25
24
"path/filepath"
26
- "sort"
27
25
"strings"
28
- "sync"
29
26
"time"
30
27
31
- "github.com/fluxcd/source-controller/pkg/azure "
28
+ "github.com/opencontainers/go-digest "
32
29
"golang.org/x/sync/errgroup"
33
30
"golang.org/x/sync/semaphore"
34
31
corev1 "k8s.io/api/core/v1"
@@ -51,10 +48,14 @@ import (
51
48
52
49
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
53
50
"github.com/fluxcd/pkg/sourceignore"
51
+
54
52
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
53
+ intdigest "github.com/fluxcd/source-controller/internal/digest"
55
54
serror "github.com/fluxcd/source-controller/internal/error"
55
+ "github.com/fluxcd/source-controller/internal/index"
56
56
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
57
57
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
58
+ "github.com/fluxcd/source-controller/pkg/azure"
58
59
"github.com/fluxcd/source-controller/pkg/gcp"
59
60
"github.com/fluxcd/source-controller/pkg/minio"
60
61
)
@@ -154,83 +155,7 @@ type BucketProvider interface {
154
155
// bucketReconcileFunc is the function type for all the v1beta2.Bucket
155
156
// (sub)reconcile functions. The type implementations are grouped and
156
157
// executed serially to perform the complete reconcile of the object.
157
- type bucketReconcileFunc func (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , index * etagIndex , dir string ) (sreconcile.Result , error )
158
-
159
- // etagIndex is an index of storage object keys and their Etag values.
160
- type etagIndex struct {
161
- sync.RWMutex
162
- index map [string ]string
163
- }
164
-
165
- // newEtagIndex returns a new etagIndex with an empty initialized index.
166
- func newEtagIndex () * etagIndex {
167
- return & etagIndex {
168
- index : make (map [string ]string ),
169
- }
170
- }
171
-
172
- func (i * etagIndex ) Add (key , etag string ) {
173
- i .Lock ()
174
- defer i .Unlock ()
175
- i .index [key ] = etag
176
- }
177
-
178
- func (i * etagIndex ) Delete (key string ) {
179
- i .Lock ()
180
- defer i .Unlock ()
181
- delete (i .index , key )
182
- }
183
-
184
- func (i * etagIndex ) Get (key string ) string {
185
- i .RLock ()
186
- defer i .RUnlock ()
187
- return i .index [key ]
188
- }
189
-
190
- func (i * etagIndex ) Has (key string ) bool {
191
- i .RLock ()
192
- defer i .RUnlock ()
193
- _ , ok := i .index [key ]
194
- return ok
195
- }
196
-
197
- func (i * etagIndex ) Index () map [string ]string {
198
- i .RLock ()
199
- defer i .RUnlock ()
200
- index := make (map [string ]string )
201
- for k , v := range i .index {
202
- index [k ] = v
203
- }
204
- return index
205
- }
206
-
207
- func (i * etagIndex ) Len () int {
208
- i .RLock ()
209
- defer i .RUnlock ()
210
- return len (i .index )
211
- }
212
-
213
- // Revision calculates the SHA256 checksum of the index.
214
- // The keys are stable sorted, and the SHA256 sum is then calculated for the
215
- // string representation of the key/value pairs, each pair written on a newline
216
- // with a space between them. The sum result is returned as a string.
217
- func (i * etagIndex ) Revision () (string , error ) {
218
- i .RLock ()
219
- defer i .RUnlock ()
220
- keyIndex := make ([]string , 0 , len (i .index ))
221
- for k := range i .index {
222
- keyIndex = append (keyIndex , k )
223
- }
224
-
225
- sort .Strings (keyIndex )
226
- sum := sha256 .New ()
227
- for _ , k := range keyIndex {
228
- if _ , err := sum .Write ([]byte (fmt .Sprintf ("%s %s\n " , k , i .index [k ]))); err != nil {
229
- return "" , err
230
- }
231
- }
232
- return fmt .Sprintf ("%x" , sum .Sum (nil )), nil
233
- }
158
+ type bucketReconcileFunc func (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , index * index.Digester , dir string ) (sreconcile.Result , error )
234
159
235
160
func (r * BucketReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
236
161
return r .SetupWithManagerAndOptions (mgr , BucketReconcilerOptions {})
@@ -371,7 +296,7 @@ func (r *BucketReconciler) reconcile(ctx context.Context, sp *patch.SerialPatche
371
296
var (
372
297
res sreconcile.Result
373
298
resErr error
374
- index = newEtagIndex ()
299
+ index = index . NewDigester ()
375
300
)
376
301
377
302
for _ , rec := range reconcilers {
@@ -397,7 +322,7 @@ func (r *BucketReconciler) reconcile(ctx context.Context, sp *patch.SerialPatche
397
322
}
398
323
399
324
// notify emits notification related to the reconciliation.
400
- func (r * BucketReconciler ) notify (ctx context.Context , oldObj , newObj * sourcev1.Bucket , index * etagIndex , res sreconcile.Result , resErr error ) {
325
+ func (r * BucketReconciler ) notify (ctx context.Context , oldObj , newObj * sourcev1.Bucket , index * index. Digester , res sreconcile.Result , resErr error ) {
401
326
// Notify successful reconciliation for new artifact and recovery from any
402
327
// failure.
403
328
if resErr == nil && res == sreconcile .ResultSuccess && newObj .Status .Artifact != nil {
@@ -443,7 +368,7 @@ func (r *BucketReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.
443
368
// condition is added.
444
369
// The hostname of any URL in the Status of the object are updated, to ensure
445
370
// they match the Storage server hostname of current runtime.
446
- func (r * BucketReconciler ) reconcileStorage (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , _ * etagIndex , _ string ) (sreconcile.Result , error ) {
371
+ func (r * BucketReconciler ) reconcileStorage (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , _ * index. Digester , _ string ) (sreconcile.Result , error ) {
447
372
// Garbage collect previous advertised artifact(s) from storage
448
373
_ = r .garbageCollect (ctx , obj )
449
374
@@ -484,7 +409,7 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, sp *patch.Seria
484
409
// When a SecretRef is defined, it attempts to fetch the Secret before calling
485
410
// the provider. If this fails, it records v1beta2.FetchFailedCondition=True on
486
411
// the object and returns early.
487
- func (r * BucketReconciler ) reconcileSource (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , index * etagIndex , dir string ) (sreconcile.Result , error ) {
412
+ func (r * BucketReconciler ) reconcileSource (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , index * index. Digester , dir string ) (sreconcile.Result , error ) {
488
413
secret , err := r .getBucketSecret (ctx , obj )
489
414
if err != nil {
490
415
e := & serror.Event {Err : err , Reason : sourcev1 .AuthenticationFailedReason }
@@ -538,26 +463,21 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
538
463
return sreconcile .ResultEmpty , e
539
464
}
540
465
541
- // Calculate revision
542
- revision , err := index .Revision ()
543
- if err != nil {
544
- return sreconcile .ResultEmpty , & serror.Event {
545
- Err : fmt .Errorf ("failed to calculate revision: %w" , err ),
546
- Reason : meta .FailedReason ,
547
- }
466
+ // Check if index has changed compared to current Artifact revision.
467
+ var changed bool
468
+ if artifact := obj .Status .Artifact ; artifact != nil && artifact .Revision != "" {
469
+ curRev := backwardsCompatibleDigest (artifact .Revision )
470
+ changed = curRev != index .Digest (curRev .Algorithm ())
548
471
}
549
472
550
- // Mark observations about the revision on the object
551
- defer func () {
552
- // As fetchIndexFiles can make last-minute modifications to the etag
553
- // index, we need to re-calculate the revision at the end
554
- revision , err := index .Revision ()
555
- if err != nil {
556
- ctrl .LoggerFrom (ctx ).Error (err , "failed to calculate revision after fetching etag index" )
557
- return
558
- }
473
+ // Fetch the bucket objects if required to.
474
+ if artifact := obj .GetArtifact (); artifact == nil || changed {
475
+ // Mark observations about the revision on the object
476
+ defer func () {
477
+ // As fetchIndexFiles can make last-minute modifications to the etag
478
+ // index, we need to re-calculate the revision at the end
479
+ revision := index .Digest (intdigest .Canonical )
559
480
560
- if ! obj .GetArtifact ().HasRevision (revision ) {
561
481
message := fmt .Sprintf ("new upstream revision '%s'" , revision )
562
482
if obj .GetArtifact () != nil {
563
483
conditions .MarkTrue (obj , sourcev1 .ArtifactOutdatedCondition , "NewRevision" , message )
@@ -567,10 +487,8 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
567
487
ctrl .LoggerFrom (ctx ).Error (err , "failed to patch" )
568
488
return
569
489
}
570
- }
571
- }()
490
+ }()
572
491
573
- if ! obj .GetArtifact ().HasRevision (revision ) {
574
492
if err = fetchIndexFiles (ctx , provider , obj , index , dir ); err != nil {
575
493
e := & serror.Event {Err : err , Reason : sourcev1 .BucketOperationFailedReason }
576
494
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
@@ -591,32 +509,32 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
591
509
// early.
592
510
// On a successful archive, the Artifact in the Status of the object is set,
593
511
// and the symlink in the Storage is updated to its path.
594
- func (r * BucketReconciler ) reconcileArtifact (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , index * etagIndex , dir string ) (sreconcile.Result , error ) {
512
+ func (r * BucketReconciler ) reconcileArtifact (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.Bucket , index * index. Digester , dir string ) (sreconcile.Result , error ) {
595
513
// Calculate revision
596
- revision , err := index .Revision ()
597
- if err != nil {
598
- return sreconcile .ResultEmpty , & serror.Event {
599
- Err : fmt .Errorf ("failed to calculate revision of new artifact: %w" , err ),
600
- Reason : meta .FailedReason ,
601
- }
602
- }
514
+ revision := index .Digest (intdigest .Canonical )
603
515
604
516
// Create artifact
605
- artifact := r .Storage .NewArtifactFor (obj .Kind , obj , revision , fmt .Sprintf ("%s.tar.gz" , revision ))
517
+ artifact := r .Storage .NewArtifactFor (obj .Kind , obj , revision . String () , fmt .Sprintf ("%s.tar.gz" , revision . Encoded () ))
606
518
607
519
// Set the ArtifactInStorageCondition if there's no drift.
608
520
defer func () {
609
- if obj .GetArtifact ().HasRevision (artifact .Revision ) {
610
- conditions .Delete (obj , sourcev1 .ArtifactOutdatedCondition )
611
- conditions .MarkTrue (obj , sourcev1 .ArtifactInStorageCondition , meta .SucceededReason ,
612
- "stored artifact: revision '%s'" , artifact .Revision )
521
+ if curArtifact := obj .GetArtifact (); curArtifact != nil && curArtifact .Revision != "" {
522
+ curRev := backwardsCompatibleDigest (curArtifact .Revision )
523
+ if index .Digest (curRev .Algorithm ()) == curRev {
524
+ conditions .Delete (obj , sourcev1 .ArtifactOutdatedCondition )
525
+ conditions .MarkTrue (obj , sourcev1 .ArtifactInStorageCondition , meta .SucceededReason ,
526
+ "stored artifact: revision '%s'" , artifact .Revision )
527
+ }
613
528
}
614
529
}()
615
530
616
531
// The artifact is up-to-date
617
- if obj .GetArtifact ().HasRevision (artifact .Revision ) {
618
- r .eventLogf (ctx , obj , eventv1 .EventTypeTrace , sourcev1 .ArtifactUpToDateReason , "artifact up-to-date with remote revision: '%s'" , artifact .Revision )
619
- return sreconcile .ResultSuccess , nil
532
+ if curArtifact := obj .GetArtifact (); curArtifact != nil && curArtifact .Revision != "" {
533
+ curRev := backwardsCompatibleDigest (curArtifact .Revision )
534
+ if index .Digest (curRev .Algorithm ()) == curRev {
535
+ r .eventLogf (ctx , obj , eventv1 .EventTypeTrace , sourcev1 .ArtifactUpToDateReason , "artifact up-to-date with remote revision: '%s'" , artifact .Revision )
536
+ return sreconcile .ResultSuccess , nil
537
+ }
620
538
}
621
539
622
540
// Ensure target path exists and is a directory
@@ -781,7 +699,7 @@ func (r *BucketReconciler) annotatedEventLogf(ctx context.Context,
781
699
// bucket using the given provider, while filtering them using .sourceignore
782
700
// rules. After fetching an object, the etag value in the index is updated to
783
701
// the current value to ensure accuracy.
784
- func fetchEtagIndex (ctx context.Context , provider BucketProvider , obj * sourcev1.Bucket , index * etagIndex , tempDir string ) error {
702
+ func fetchEtagIndex (ctx context.Context , provider BucketProvider , obj * sourcev1.Bucket , index * index. Digester , tempDir string ) error {
785
703
ctxTimeout , cancel := context .WithTimeout (ctx , obj .Spec .Timeout .Duration )
786
704
defer cancel ()
787
705
@@ -835,7 +753,7 @@ func fetchEtagIndex(ctx context.Context, provider BucketProvider, obj *sourcev1.
835
753
// using the given provider, and stores them into tempDir. It downloads in
836
754
// parallel, but limited to the maxConcurrentBucketFetches.
837
755
// Given an index is provided, the bucket is assumed to exist.
838
- func fetchIndexFiles (ctx context.Context , provider BucketProvider , obj * sourcev1.Bucket , index * etagIndex , tempDir string ) error {
756
+ func fetchIndexFiles (ctx context.Context , provider BucketProvider , obj * sourcev1.Bucket , index * index. Digester , tempDir string ) error {
839
757
ctxTimeout , cancel := context .WithTimeout (ctx , obj .Spec .Timeout .Duration )
840
758
defer cancel ()
841
759
@@ -879,3 +797,10 @@ func fetchIndexFiles(ctx context.Context, provider BucketProvider, obj *sourcev1
879
797
880
798
return nil
881
799
}
800
+
801
+ func backwardsCompatibleDigest (d string ) digest.Digest {
802
+ if ! strings .Contains (d , ":" ) {
803
+ d = digest .SHA256 .String () + ":" + d
804
+ }
805
+ return digest .Digest (d )
806
+ }
0 commit comments