Skip to content

Commit d997876

Browse files
darkowlzzhiddeco
authored andcommitted
Make generic SummarizeAndPatch()
summarizeAndPatch() was used by all the reconcilers with their own object type. This creates a generic SummarizeAndPatch helper that takes a conditions.Setter object and performs the same operations. All the reconcilers are updated to use SummarizeAndPatch(). The process of summarize and patch can be configured using the HelperOptions. Introduce ResultProcessor to allow injecting middlewares in the SummarizeAndPatch process. Introduce RuntimeResultBuilder to allow defining how the reconciliation result is computed for specific reconciler. This enabled different reconcilers to have different meanings of the reconciliation results. Introduce Conditions in summary package to store all the status conditions related information of a reconciler. This is passed to SummarizeAndPatch() to be used for summary and patch calculation. Remove all the redundant summarizeAndPatch() tests per reconciler. Add package internal/object containing helpers for interacting with runtime.Object needed by the generic SummarizeAndPatch(). Add tests for ComputeReconcileResult(). Signed-off-by: Sunny <[email protected]>
1 parent 9b56137 commit d997876

17 files changed

+1474
-687
lines changed

controllers/bucket_controller.go

Lines changed: 37 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,8 @@ import (
3737
"golang.org/x/sync/semaphore"
3838
"google.golang.org/api/option"
3939
corev1 "k8s.io/api/core/v1"
40-
apierrors "k8s.io/apimachinery/pkg/api/errors"
4140
"k8s.io/apimachinery/pkg/runtime"
4241
"k8s.io/apimachinery/pkg/types"
43-
kerrors "k8s.io/apimachinery/pkg/util/errors"
4442
kuberecorder "k8s.io/client-go/tools/record"
4543
ctrl "sigs.k8s.io/controller-runtime"
4644
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -57,33 +55,33 @@ import (
5755
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
5856
serror "github.com/fluxcd/source-controller/internal/error"
5957
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
58+
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
6059
"github.com/fluxcd/source-controller/pkg/sourceignore"
6160
)
6261

63-
// Status conditions owned by Bucket reconciler.
64-
var bucketOwnedConditions = []string{
65-
sourcev1.ArtifactOutdatedCondition,
66-
sourcev1.FetchFailedCondition,
67-
meta.ReadyCondition,
68-
meta.ReconcilingCondition,
69-
meta.StalledCondition,
70-
}
71-
72-
// Conditions that Ready condition is influenced by in descending order of their
73-
// priority.
74-
var bucketReadyDeps = []string{
75-
sourcev1.ArtifactOutdatedCondition,
76-
sourcev1.FetchFailedCondition,
77-
meta.StalledCondition,
78-
meta.ReconcilingCondition,
79-
}
80-
81-
// Negative conditions that Ready condition is influenced by.
82-
var bucketReadyDepsNegative = []string{
83-
sourcev1.ArtifactOutdatedCondition,
84-
sourcev1.FetchFailedCondition,
85-
meta.StalledCondition,
86-
meta.ReconcilingCondition,
62+
// bucketReadyConditions contains all the conditions information needed
63+
// for Bucket Ready status conditions summary calculation.
64+
var bucketReadyConditions = summarize.Conditions{
65+
Target: meta.ReadyCondition,
66+
Owned: []string{
67+
sourcev1.ArtifactOutdatedCondition,
68+
sourcev1.FetchFailedCondition,
69+
meta.ReadyCondition,
70+
meta.ReconcilingCondition,
71+
meta.StalledCondition,
72+
},
73+
Summarize: []string{
74+
sourcev1.ArtifactOutdatedCondition,
75+
sourcev1.FetchFailedCondition,
76+
meta.StalledCondition,
77+
meta.ReconcilingCondition,
78+
},
79+
NegativePolarity: []string{
80+
sourcev1.ArtifactOutdatedCondition,
81+
sourcev1.FetchFailedCondition,
82+
meta.StalledCondition,
83+
meta.ReconcilingCondition,
84+
},
8785
}
8886

8987
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=buckets,verbs=get;list;watch;create;update;patch;delete
@@ -151,7 +149,19 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
151149
// Always attempt to patch the object and status after each reconciliation
152150
// NOTE: The final runtime result and error are set in this block.
153151
defer func() {
154-
result, retErr = r.summarizeAndPatch(ctx, obj, patchHelper, recResult, retErr)
152+
summarizeHelper := summarize.NewHelper(r.EventRecorder, patchHelper)
153+
summarizeOpts := []summarize.Option{
154+
summarize.WithConditions(bucketReadyConditions),
155+
summarize.WithReconcileResult(recResult),
156+
summarize.WithReconcileError(retErr),
157+
summarize.WithIgnoreNotFound(),
158+
summarize.WithProcessors(
159+
summarize.RecordContextualError,
160+
summarize.RecordReconcileReq,
161+
),
162+
summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetInterval().Duration}),
163+
}
164+
result, retErr = summarizeHelper.SummarizeAndPatch(ctx, obj, summarizeOpts...)
155165

156166
// Always record readiness and duration metrics
157167
r.Metrics.RecordReadiness(ctx, obj)
@@ -181,50 +191,6 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
181191
return
182192
}
183193

184-
// summarizeAndPatch analyzes the object conditions to create a summary of the
185-
// status conditions, computes runtime results and patches the object in the K8s
186-
// API server.
187-
func (r *BucketReconciler) summarizeAndPatch(
188-
ctx context.Context,
189-
obj *sourcev1.Bucket,
190-
patchHelper *patch.Helper,
191-
res sreconcile.Result,
192-
recErr error) (ctrl.Result, error) {
193-
sreconcile.RecordContextualError(ctx, r.EventRecorder, obj, recErr)
194-
195-
// Record the value of the reconciliation request if any.
196-
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
197-
obj.Status.SetLastHandledReconcileRequest(v)
198-
}
199-
200-
// Compute the reconcile results, obtain patch options and reconcile error.
201-
var patchOpts []patch.Option
202-
var result ctrl.Result
203-
patchOpts, result, recErr = sreconcile.ComputeReconcileResult(obj, obj.GetRequeueAfter(), res, recErr, bucketOwnedConditions)
204-
205-
// Summarize the Ready condition based on abnormalities that may have been observed.
206-
conditions.SetSummary(obj,
207-
meta.ReadyCondition,
208-
conditions.WithConditions(
209-
bucketReadyDeps...,
210-
),
211-
conditions.WithNegativePolarityConditions(
212-
bucketReadyDepsNegative...,
213-
),
214-
)
215-
216-
// Finally, patch the resource.
217-
if err := patchHelper.Patch(ctx, obj, patchOpts...); err != nil {
218-
// Ignore patch error "not found" when the object is being deleted.
219-
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
220-
err = kerrors.FilterOut(err, func(e error) bool { return apierrors.IsNotFound(e) })
221-
}
222-
recErr = kerrors.NewAggregate([]error{recErr, err})
223-
}
224-
225-
return result, recErr
226-
}
227-
228194
// reconcile steps iterates through the actual reconciliation tasks for objec,
229195
// it returns early on the first step that returns ResultRequeue or produces an
230196
// error.

controllers/bucket_controller_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/darkowlzz/controller-check/status"
3535
"github.com/fluxcd/pkg/apis/meta"
3636
"github.com/fluxcd/pkg/runtime/conditions"
37+
"github.com/fluxcd/pkg/runtime/patch"
3738
. "github.com/onsi/gomega"
3839
raw "google.golang.org/api/storage/v1"
3940
corev1 "k8s.io/api/core/v1"
@@ -125,10 +126,25 @@ func TestBucketReconciler_Reconcile(t *testing.T) {
125126
}, timeout).Should(BeTrue())
126127

127128
// Check if the object status is valid.
128-
condns := &status.Conditions{NegativePolarity: bucketReadyDepsNegative}
129+
condns := &status.Conditions{NegativePolarity: bucketReadyConditions.NegativePolarity}
129130
checker := status.NewChecker(testEnv.Client, testEnv.GetScheme(), condns)
130131
checker.CheckErr(ctx, obj)
131132

133+
// Patch the object with reconcile request annotation.
134+
patchHelper, err := patch.NewHelper(obj, testEnv.Client)
135+
g.Expect(err).ToNot(HaveOccurred())
136+
annotations := map[string]string{
137+
meta.ReconcileRequestAnnotation: "now",
138+
}
139+
obj.SetAnnotations(annotations)
140+
g.Expect(patchHelper.Patch(ctx, obj)).ToNot(HaveOccurred())
141+
g.Eventually(func() bool {
142+
if err := testEnv.Get(ctx, key, obj); err != nil {
143+
return false
144+
}
145+
return obj.Status.LastHandledReconcileAt == "now"
146+
}, timeout).Should(BeTrue())
147+
132148
g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
133149

134150
// Wait for Bucket to be deleted

controllers/gitrepository_controller.go

Lines changed: 42 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ import (
2727
securejoin "github.com/cyphar/filepath-securejoin"
2828
"github.com/fluxcd/pkg/runtime/logger"
2929
corev1 "k8s.io/api/core/v1"
30-
apierrors "k8s.io/apimachinery/pkg/api/errors"
3130
"k8s.io/apimachinery/pkg/runtime"
3231
"k8s.io/apimachinery/pkg/types"
33-
kerrors "k8s.io/apimachinery/pkg/util/errors"
3432
kuberecorder "k8s.io/client-go/tools/record"
3533
ctrl "sigs.k8s.io/controller-runtime"
3634
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -49,41 +47,41 @@ import (
4947
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
5048
serror "github.com/fluxcd/source-controller/internal/error"
5149
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
50+
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
5251
"github.com/fluxcd/source-controller/internal/util"
5352
"github.com/fluxcd/source-controller/pkg/git"
5453
"github.com/fluxcd/source-controller/pkg/git/strategy"
5554
"github.com/fluxcd/source-controller/pkg/sourceignore"
5655
)
5756

58-
// Status conditions owned by the GitRepository reconciler.
59-
var gitRepoOwnedConditions = []string{
60-
sourcev1.SourceVerifiedCondition,
61-
sourcev1.FetchFailedCondition,
62-
sourcev1.IncludeUnavailableCondition,
63-
sourcev1.ArtifactOutdatedCondition,
64-
meta.ReadyCondition,
65-
meta.ReconcilingCondition,
66-
meta.StalledCondition,
67-
}
68-
69-
// Conditions that Ready condition is influenced by in descending order of their
70-
// priority.
71-
var gitRepoReadyDeps = []string{
72-
sourcev1.IncludeUnavailableCondition,
73-
sourcev1.SourceVerifiedCondition,
74-
sourcev1.FetchFailedCondition,
75-
sourcev1.ArtifactOutdatedCondition,
76-
meta.StalledCondition,
77-
meta.ReconcilingCondition,
78-
}
79-
80-
// Negative conditions that Ready condition is influenced by.
81-
var gitRepoReadyDepsNegative = []string{
82-
sourcev1.FetchFailedCondition,
83-
sourcev1.IncludeUnavailableCondition,
84-
sourcev1.ArtifactOutdatedCondition,
85-
meta.StalledCondition,
86-
meta.ReconcilingCondition,
57+
// gitRepoReadyConditions contains all the conditions information needed
58+
// for GitRepository Ready status conditions summary calculation.
59+
var gitRepoReadyConditions = summarize.Conditions{
60+
Target: meta.ReadyCondition,
61+
Owned: []string{
62+
sourcev1.SourceVerifiedCondition,
63+
sourcev1.FetchFailedCondition,
64+
sourcev1.IncludeUnavailableCondition,
65+
sourcev1.ArtifactOutdatedCondition,
66+
meta.ReadyCondition,
67+
meta.ReconcilingCondition,
68+
meta.StalledCondition,
69+
},
70+
Summarize: []string{
71+
sourcev1.IncludeUnavailableCondition,
72+
sourcev1.SourceVerifiedCondition,
73+
sourcev1.FetchFailedCondition,
74+
sourcev1.ArtifactOutdatedCondition,
75+
meta.StalledCondition,
76+
meta.ReconcilingCondition,
77+
},
78+
NegativePolarity: []string{
79+
sourcev1.FetchFailedCondition,
80+
sourcev1.IncludeUnavailableCondition,
81+
sourcev1.ArtifactOutdatedCondition,
82+
meta.StalledCondition,
83+
meta.ReconcilingCondition,
84+
},
8785
}
8886

8987
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch;create;update;patch;delete
@@ -157,7 +155,19 @@ func (r *GitRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques
157155
// Always attempt to patch the object and status after each reconciliation
158156
// NOTE: The final runtime result and error are set in this block.
159157
defer func() {
160-
result, retErr = r.summarizeAndPatch(ctx, obj, patchHelper, recResult, retErr)
158+
summarizeHelper := summarize.NewHelper(r.EventRecorder, patchHelper)
159+
summarizeOpts := []summarize.Option{
160+
summarize.WithConditions(gitRepoReadyConditions),
161+
summarize.WithReconcileResult(recResult),
162+
summarize.WithReconcileError(retErr),
163+
summarize.WithIgnoreNotFound(),
164+
summarize.WithProcessors(
165+
summarize.RecordContextualError,
166+
summarize.RecordReconcileReq,
167+
),
168+
summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetInterval().Duration}),
169+
}
170+
result, retErr = summarizeHelper.SummarizeAndPatch(ctx, obj, summarizeOpts...)
161171

162172
// Always record readiness and duration metrics
163173
r.Metrics.RecordReadiness(ctx, obj)
@@ -189,50 +199,6 @@ func (r *GitRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques
189199
return
190200
}
191201

192-
// summarizeAndPatch analyzes the object conditions to create a summary of the
193-
// status conditions, computes runtime results and patches the object in the K8s
194-
// API server.
195-
func (r *GitRepositoryReconciler) summarizeAndPatch(
196-
ctx context.Context,
197-
obj *sourcev1.GitRepository,
198-
patchHelper *patch.Helper,
199-
res sreconcile.Result,
200-
recErr error) (ctrl.Result, error) {
201-
sreconcile.RecordContextualError(ctx, r.EventRecorder, obj, recErr)
202-
203-
// Record the value of the reconciliation request if any.
204-
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
205-
obj.Status.SetLastHandledReconcileRequest(v)
206-
}
207-
208-
// Compute the reconcile results, obtain patch options and reconcile error.
209-
var patchOpts []patch.Option
210-
var result ctrl.Result
211-
patchOpts, result, recErr = sreconcile.ComputeReconcileResult(obj, obj.GetRequeueAfter(), res, recErr, gitRepoOwnedConditions)
212-
213-
// Summarize the Ready condition based on abnormalities that may have been observed.
214-
conditions.SetSummary(obj,
215-
meta.ReadyCondition,
216-
conditions.WithConditions(
217-
gitRepoReadyDeps...,
218-
),
219-
conditions.WithNegativePolarityConditions(
220-
gitRepoReadyDepsNegative...,
221-
),
222-
)
223-
224-
// Finally, patch the resource.
225-
if err := patchHelper.Patch(ctx, obj, patchOpts...); err != nil {
226-
// Ignore patch error "not found" when the object is being deleted.
227-
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
228-
err = kerrors.FilterOut(err, func(e error) bool { return apierrors.IsNotFound(e) })
229-
}
230-
recErr = kerrors.NewAggregate([]error{recErr, err})
231-
}
232-
233-
return result, recErr
234-
}
235-
236202
// reconcile steps iterates through the actual reconciliation tasks for objec,
237203
// it returns early on the first step that returns ResultRequeue or produces an
238204
// error.

controllers/gitrepository_controller_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/fluxcd/pkg/apis/meta"
3131
"github.com/fluxcd/pkg/gittestserver"
3232
"github.com/fluxcd/pkg/runtime/conditions"
33+
"github.com/fluxcd/pkg/runtime/patch"
3334
"github.com/fluxcd/pkg/ssh"
3435
"github.com/fluxcd/pkg/testserver"
3536
"github.com/go-git/go-billy/v5/memfs"
@@ -190,10 +191,25 @@ func TestGitRepositoryReconciler_Reconcile(t *testing.T) {
190191
}, timeout).Should(BeTrue())
191192

192193
// Check if the object status is valid.
193-
condns := &status.Conditions{NegativePolarity: gitRepoReadyDepsNegative}
194+
condns := &status.Conditions{NegativePolarity: gitRepoReadyConditions.NegativePolarity}
194195
checker := status.NewChecker(testEnv.Client, testEnv.GetScheme(), condns)
195196
checker.CheckErr(ctx, obj)
196197

198+
// Patch the object with reconcile request annotation.
199+
patchHelper, err := patch.NewHelper(obj, testEnv.Client)
200+
g.Expect(err).ToNot(HaveOccurred())
201+
annotations := map[string]string{
202+
meta.ReconcileRequestAnnotation: "now",
203+
}
204+
obj.SetAnnotations(annotations)
205+
g.Expect(patchHelper.Patch(ctx, obj)).ToNot(HaveOccurred())
206+
g.Eventually(func() bool {
207+
if err := testEnv.Get(ctx, key, obj); err != nil {
208+
return false
209+
}
210+
return obj.Status.LastHandledReconcileAt == "now"
211+
}, timeout).Should(BeTrue())
212+
197213
g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
198214

199215
// Wait for GitRepository to be deleted

0 commit comments

Comments
 (0)