Skip to content

Commit ae0b38c

Browse files
authored
Merge pull request #646 from fluxcd/positive-conditions
2 parents de8f32b + b869716 commit ae0b38c

15 files changed

+554
-68
lines changed

api/v1beta2/condition_types.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,24 @@ package v1beta2
1919
const SourceFinalizer = "finalizers.fluxcd.io"
2020

2121
const (
22+
// ArtifactInStorageCondition indicates the availability of the Artifact in
23+
// the storage.
24+
// If True, the Artifact is stored successfully.
25+
// This Condition is only present on the resource if the Artifact is
26+
// successfully stored.
27+
ArtifactInStorageCondition string = "ArtifactInStorage"
28+
2229
// ArtifactOutdatedCondition indicates the current Artifact of the Source
2330
// is outdated.
2431
// This is a "negative polarity" or "abnormal-true" type, and is only
2532
// present on the resource if it is True.
2633
ArtifactOutdatedCondition string = "ArtifactOutdated"
2734

28-
// SourceVerifiedCondition indicates the integrity of the Source has been
29-
// verified. If True, the integrity check succeeded. If False, it failed.
30-
// The Condition is only present on the resource if the integrity has been
31-
// verified.
35+
// SourceVerifiedCondition indicates the integrity verification of the
36+
// Source.
37+
// If True, the integrity check succeeded. If False, it failed.
38+
// This Condition is only present on the resource if the integrity check
39+
// is enabled.
3240
SourceVerifiedCondition string = "SourceVerified"
3341

3442
// FetchFailedCondition indicates a transient or persistent fetch failure
@@ -85,4 +93,8 @@ const (
8593

8694
// SymlinkUpdateFailedReason signals a failure in updating a symlink.
8795
SymlinkUpdateFailedReason string = "SymlinkUpdateFailed"
96+
97+
// ArtifactUpToDateReason signals that an existing Artifact is up-to-date
98+
// with the Source.
99+
ArtifactUpToDateReason string = "ArtifactUpToDate"
88100
)

controllers/bucket_controller.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,25 @@ const maxConcurrentBucketFetches = 100
7474
var bucketReadyCondition = summarize.Conditions{
7575
Target: meta.ReadyCondition,
7676
Owned: []string{
77-
sourcev1.FetchFailedCondition,
7877
sourcev1.StorageOperationFailedCondition,
78+
sourcev1.FetchFailedCondition,
7979
sourcev1.ArtifactOutdatedCondition,
80+
sourcev1.ArtifactInStorageCondition,
8081
meta.ReadyCondition,
8182
meta.ReconcilingCondition,
8283
meta.StalledCondition,
8384
},
8485
Summarize: []string{
85-
sourcev1.FetchFailedCondition,
8686
sourcev1.StorageOperationFailedCondition,
87+
sourcev1.FetchFailedCondition,
8788
sourcev1.ArtifactOutdatedCondition,
89+
sourcev1.ArtifactInStorageCondition,
8890
meta.StalledCondition,
8991
meta.ReconcilingCondition,
9092
},
9193
NegativePolarity: []string{
92-
sourcev1.FetchFailedCondition,
9394
sourcev1.StorageOperationFailedCondition,
95+
sourcev1.FetchFailedCondition,
9496
sourcev1.ArtifactOutdatedCondition,
9597
meta.StalledCondition,
9698
meta.ReconcilingCondition,
@@ -375,11 +377,14 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, obj *sourcev1.B
375377
if artifact := obj.GetArtifact(); artifact != nil && !r.Storage.ArtifactExist(*artifact) {
376378
obj.Status.Artifact = nil
377379
obj.Status.URL = ""
380+
// Remove the condition as the artifact doesn't exist.
381+
conditions.Delete(obj, sourcev1.ArtifactInStorageCondition)
378382
}
379383

380384
// Record that we do not have an artifact
381385
if obj.GetArtifact() == nil {
382386
conditions.MarkReconciling(obj, "NoArtifact", "no artifact for resource in storage")
387+
conditions.Delete(obj, sourcev1.ArtifactInStorageCondition)
383388
return sreconcile.ResultSuccess, nil
384389
}
385390

@@ -510,18 +515,18 @@ func (r *BucketReconciler) reconcileArtifact(ctx context.Context, obj *sourcev1.
510515
// Create artifact
511516
artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision, fmt.Sprintf("%s.tar.gz", revision))
512517

513-
// Always restore the Ready condition in case it got removed due to a transient error
518+
// Set the ArtifactInStorageCondition if there's no drift.
514519
defer func() {
515520
if obj.GetArtifact().HasRevision(artifact.Revision) {
516521
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
517-
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason,
522+
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
518523
"stored artifact for revision '%s'", artifact.Revision)
519524
}
520525
}()
521526

522527
// The artifact is up-to-date
523528
if obj.GetArtifact().HasRevision(artifact.Revision) {
524-
ctrl.LoggerFrom(ctx).Info("artifact up-to-date", "revision", artifact.Revision)
529+
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision)
525530
return sreconcile.ResultSuccess, nil
526531
}
527532

controllers/bucket_controller_test.go

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ import (
3838
"k8s.io/client-go/tools/record"
3939
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
4040
"sigs.k8s.io/controller-runtime/pkg/client"
41+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
4142
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
4243

4344
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
4445
gcsmock "github.com/fluxcd/source-controller/internal/mock/gcs"
4546
s3mock "github.com/fluxcd/source-controller/internal/mock/s3"
4647
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
48+
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
4749
)
4850

4951
// Environment variable to set the GCP Storage host for the GCP client.
@@ -886,13 +888,13 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) {
886888
assertConditions []metav1.Condition
887889
}{
888890
{
889-
name: "Archiving artifact to storage makes Ready=True",
891+
name: "Archiving artifact to storage makes ArtifactInStorage=True",
890892
beforeFunc: func(t *WithT, obj *sourcev1.Bucket, index *etagIndex, dir string) {
891893
obj.Spec.Interval = metav1.Duration{Duration: interval}
892894
},
893895
want: sreconcile.ResultSuccess,
894896
assertConditions: []metav1.Condition{
895-
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
897+
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
896898
},
897899
},
898900
{
@@ -909,7 +911,7 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) {
909911
},
910912
want: sreconcile.ResultSuccess,
911913
assertConditions: []metav1.Condition{
912-
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
914+
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
913915
},
914916
},
915917
{
@@ -920,7 +922,7 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) {
920922
},
921923
want: sreconcile.ResultSuccess,
922924
assertConditions: []metav1.Condition{
923-
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
925+
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
924926
},
925927
},
926928
{
@@ -937,7 +939,7 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) {
937939
},
938940
want: sreconcile.ResultSuccess,
939941
assertConditions: []metav1.Condition{
940-
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
942+
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"),
941943
},
942944
},
943945
{
@@ -1070,3 +1072,94 @@ func Test_etagIndex_Revision(t *testing.T) {
10701072
})
10711073
}
10721074
}
1075+
1076+
func TestBucketReconciler_statusConditions(t *testing.T) {
1077+
tests := []struct {
1078+
name string
1079+
beforeFunc func(obj *sourcev1.Bucket)
1080+
assertConditions []metav1.Condition
1081+
}{
1082+
{
1083+
name: "positive conditions only",
1084+
beforeFunc: func(obj *sourcev1.Bucket) {
1085+
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision")
1086+
},
1087+
assertConditions: []metav1.Condition{
1088+
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "stored artifact for revision"),
1089+
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision"),
1090+
},
1091+
},
1092+
{
1093+
name: "multiple failures",
1094+
beforeFunc: func(obj *sourcev1.Bucket) {
1095+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret")
1096+
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, sourcev1.DirCreationFailedReason, "failed to create directory")
1097+
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", "some error")
1098+
},
1099+
assertConditions: []metav1.Condition{
1100+
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.DirCreationFailedReason, "failed to create directory"),
1101+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret"),
1102+
*conditions.TrueCondition(sourcev1.StorageOperationFailedCondition, sourcev1.DirCreationFailedReason, "failed to create directory"),
1103+
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "some error"),
1104+
},
1105+
},
1106+
{
1107+
name: "mixed positive and negative conditions",
1108+
beforeFunc: func(obj *sourcev1.Bucket) {
1109+
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision")
1110+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret")
1111+
},
1112+
assertConditions: []metav1.Condition{
1113+
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "failed to get secret"),
1114+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret"),
1115+
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for revision"),
1116+
},
1117+
},
1118+
}
1119+
1120+
for _, tt := range tests {
1121+
t.Run(tt.name, func(t *testing.T) {
1122+
g := NewWithT(t)
1123+
1124+
obj := &sourcev1.Bucket{
1125+
TypeMeta: metav1.TypeMeta{
1126+
Kind: sourcev1.BucketKind,
1127+
APIVersion: "source.toolkit.fluxcd.io/v1beta2",
1128+
},
1129+
ObjectMeta: metav1.ObjectMeta{
1130+
Name: "bucket",
1131+
Namespace: "foo",
1132+
},
1133+
}
1134+
clientBuilder := fake.NewClientBuilder()
1135+
clientBuilder.WithObjects(obj)
1136+
c := clientBuilder.Build()
1137+
1138+
patchHelper, err := patch.NewHelper(obj, c)
1139+
g.Expect(err).ToNot(HaveOccurred())
1140+
1141+
if tt.beforeFunc != nil {
1142+
tt.beforeFunc(obj)
1143+
}
1144+
1145+
ctx := context.TODO()
1146+
recResult := sreconcile.ResultSuccess
1147+
var retErr error
1148+
1149+
summarizeHelper := summarize.NewHelper(record.NewFakeRecorder(32), patchHelper)
1150+
summarizeOpts := []summarize.Option{
1151+
summarize.WithConditions(bucketReadyCondition),
1152+
summarize.WithReconcileResult(recResult),
1153+
summarize.WithReconcileError(retErr),
1154+
summarize.WithIgnoreNotFound(),
1155+
summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetRequeueAfter()}),
1156+
summarize.WithPatchFieldOwner("source-controller"),
1157+
}
1158+
_, retErr = summarizeHelper.SummarizeAndPatch(ctx, obj, summarizeOpts...)
1159+
1160+
key := client.ObjectKeyFromObject(obj)
1161+
g.Expect(c.Get(ctx, key, obj)).ToNot(HaveOccurred())
1162+
g.Expect(obj.GetConditions()).To(conditions.MatchConditions(tt.assertConditions))
1163+
})
1164+
}
1165+
}

controllers/gitrepository_controller.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,30 @@ import (
6161
var gitRepositoryReadyCondition = summarize.Conditions{
6262
Target: meta.ReadyCondition,
6363
Owned: []string{
64-
sourcev1.SourceVerifiedCondition,
65-
sourcev1.FetchFailedCondition,
6664
sourcev1.StorageOperationFailedCondition,
65+
sourcev1.FetchFailedCondition,
6766
sourcev1.IncludeUnavailableCondition,
6867
sourcev1.ArtifactOutdatedCondition,
68+
sourcev1.ArtifactInStorageCondition,
69+
sourcev1.SourceVerifiedCondition,
6970
meta.ReadyCondition,
7071
meta.ReconcilingCondition,
7172
meta.StalledCondition,
7273
},
7374
Summarize: []string{
74-
sourcev1.IncludeUnavailableCondition,
75-
sourcev1.SourceVerifiedCondition,
76-
sourcev1.FetchFailedCondition,
7775
sourcev1.StorageOperationFailedCondition,
76+
sourcev1.FetchFailedCondition,
77+
sourcev1.IncludeUnavailableCondition,
7878
sourcev1.ArtifactOutdatedCondition,
79+
sourcev1.ArtifactInStorageCondition,
80+
sourcev1.SourceVerifiedCondition,
7981
meta.StalledCondition,
8082
meta.ReconcilingCondition,
8183
},
8284
NegativePolarity: []string{
85+
sourcev1.StorageOperationFailedCondition,
8386
sourcev1.FetchFailedCondition,
8487
sourcev1.IncludeUnavailableCondition,
85-
sourcev1.StorageOperationFailedCondition,
8688
sourcev1.ArtifactOutdatedCondition,
8789
meta.StalledCondition,
8890
meta.ReconcilingCondition,
@@ -279,11 +281,14 @@ func (r *GitRepositoryReconciler) reconcileStorage(ctx context.Context,
279281
if artifact := obj.GetArtifact(); artifact != nil && !r.Storage.ArtifactExist(*artifact) {
280282
obj.Status.Artifact = nil
281283
obj.Status.URL = ""
284+
// Remove the condition as the artifact doesn't exist.
285+
conditions.Delete(obj, sourcev1.ArtifactInStorageCondition)
282286
}
283287

284288
// Record that we do not have an artifact
285289
if obj.GetArtifact() == nil {
286290
conditions.MarkReconciling(obj, "NoArtifact", "no artifact for resource in storage")
291+
conditions.Delete(obj, sourcev1.ArtifactInStorageCondition)
287292
return sreconcile.ResultSuccess, nil
288293
}
289294

@@ -446,18 +451,18 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
446451
// Create potential new artifact with current available metadata
447452
artifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), commit.String(), fmt.Sprintf("%s.tar.gz", commit.Hash.String()))
448453

449-
// Always restore the Ready condition in case it got removed due to a transient error
454+
// Set the ArtifactInStorageCondition if there's no drift.
450455
defer func() {
451456
if obj.GetArtifact().HasRevision(artifact.Revision) && !includes.Diff(obj.Status.IncludedArtifacts) {
452457
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
453-
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason,
458+
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
454459
"stored artifact for revision '%s'", artifact.Revision)
455460
}
456461
}()
457462

458463
// The artifact is up-to-date
459464
if obj.GetArtifact().HasRevision(artifact.Revision) && !includes.Diff(obj.Status.IncludedArtifacts) {
460-
ctrl.LoggerFrom(ctx).Info("artifact up-to-date", "revision", artifact.Revision)
465+
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision)
461466
return sreconcile.ResultSuccess, nil
462467
}
463468

0 commit comments

Comments
 (0)