Skip to content

Commit 7081861

Browse files
committed
Requeue after interval.
Configure the RequeueAfter interval when the gates are closed.
1 parent ca9500d commit 7081861

File tree

2 files changed

+122
-20
lines changed

2 files changed

+122
-20
lines changed

controllers/kustomizationautodeployer_controller.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package controllers
1919
import (
2020
"context"
2121
"fmt"
22+
"sort"
2223
"strings"
24+
"time"
2325

2426
"github.com/fluxcd/pkg/apis/meta"
2527
"github.com/fluxcd/pkg/runtime/patch"
@@ -124,7 +126,7 @@ func (r *KustomizationAutoDeployerReconciler) Reconcile(ctx context.Context, req
124126
return ctrl.Result{}, nil
125127
}
126128

127-
// IF the last applied version in the Kustomization == the current version
129+
// If the last applied version in the Kustomization == the current version
128130
// of the GitRepository, we can look for a new version.
129131
// TODO: is this right?
130132
if kustomizationCommitID != repoCommitID {
@@ -185,8 +187,6 @@ func (r *KustomizationAutoDeployerReconciler) Reconcile(ctx context.Context, req
185187
return ctrl.Result{}, fmt.Errorf("failed to create patch helper for GitRepository: %w", err)
186188
}
187189

188-
logger.Info("identified next commit - patching GitRepository", "nextCommitID", nextCommitToDeploy, "repositoryName", gitRepository.GetName(), "repositoryNamespace", gitRepository.GetNamespace())
189-
190190
instantiatedGates := map[string]gates.Gate{}
191191
for k, factory := range r.GateFactories {
192192
instantiatedGates[k] = factory(logger, r.Client)
@@ -208,9 +208,16 @@ func (r *KustomizationAutoDeployerReconciler) Reconcile(ctx context.Context, req
208208
return ctrl.Result{}, err
209209
}
210210

211-
return ctrl.Result{}, nil
211+
requeueAfter, err := calculateInterval(&deployer, instantiatedGates)
212+
if err != nil {
213+
return ctrl.Result{}, fmt.Errorf("failed to calculate requeue interval: %w", err)
214+
}
215+
216+
return ctrl.Result{RequeueAfter: requeueAfter}, nil
212217
}
213218

219+
logger.Info("identified next commit - patching GitRepository", "nextCommitID", nextCommitToDeploy, "repositoryName", gitRepository.GetName(), "repositoryNamespace", gitRepository.GetNamespace())
220+
214221
gitRepository.Spec.Reference.Commit = nextCommitToDeploy
215222
if err := patchHelper.Patch(ctx, &gitRepository); err != nil {
216223
return ctrl.Result{}, fmt.Errorf("failed to update GitRepository: %w", err)
@@ -326,3 +333,33 @@ func setDeployerReadiness(deployer *deployerv1.KustomizationAutoDeployer, status
326333
}
327334
apimeta.SetStatusCondition(&deployer.Status.Conditions, newCondition)
328335
}
336+
337+
func calculateInterval(gs *deployerv1.KustomizationAutoDeployer, g map[string]gates.Gate) (time.Duration, error) {
338+
res := []time.Duration{}
339+
for _, mg := range gs.Spec.Gates {
340+
relevantGates, err := gates.FindRelevantGates(mg, g)
341+
if err != nil {
342+
return gates.NoRequeueInterval, err
343+
}
344+
345+
for _, rg := range relevantGates {
346+
d, err := rg.Interval(&mg)
347+
if err != nil {
348+
return gates.NoRequeueInterval, err
349+
}
350+
351+
if d > gates.NoRequeueInterval {
352+
res = append(res, d)
353+
}
354+
}
355+
}
356+
357+
if len(res) == 0 {
358+
return gates.NoRequeueInterval, nil
359+
}
360+
361+
// Find the lowest requeue interval provided by a gate
362+
sort.Slice(res, func(i, j int) bool { return res[i] < res[j] })
363+
364+
return res[0], nil
365+
}

controllers/kustomizationautodeployer_controller_test.go

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestReconciliation(t *testing.T) {
100100
_, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(deployer)})
101101
test.AssertErrorMatch(t, "failed to load kustomizationRef missing-kustomization-name", err)
102102

103-
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(deployer), deployer))
103+
reload(t, k8sClient, deployer)
104104
assertDeployerCondition(t, deployer, metav1.ConditionFalse, meta.ReadyCondition, deployerv1.FailedToLoadKustomizationReason, "referenced Kustomization could not be loaded")
105105
})
106106

@@ -121,7 +121,7 @@ func TestReconciliation(t *testing.T) {
121121
_, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(deployer)})
122122
test.AssertNoError(t, err)
123123

124-
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(deployer), deployer))
124+
reload(t, k8sClient, deployer)
125125
assertDeployerCondition(t, deployer, metav1.ConditionFalse, meta.ReadyCondition, deployerv1.GitRepositoryNotPopulatedReason, "GitRepository default/test-gitrepository does not have an artifact")
126126
})
127127

@@ -152,7 +152,7 @@ func TestReconciliation(t *testing.T) {
152152
_, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(deployer)})
153153
test.AssertErrorMatch(t, "not enough commit IDs to fulfill request", err)
154154

155-
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(deployer), deployer))
155+
reload(t, k8sClient, deployer)
156156
assertDeployerCondition(t, deployer, metav1.ConditionFalse, meta.ReadyCondition, deployerv1.RevisionsErrorReason, "not enough commit IDs to fulfill request")
157157
})
158158

@@ -182,19 +182,16 @@ func TestReconciliation(t *testing.T) {
182182
_, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(deployer)})
183183
test.AssertNoError(t, err)
184184

185-
updated := &deployerv1.KustomizationAutoDeployer{}
186-
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(deployer), updated))
187-
185+
reload(t, k8sClient, deployer)
188186
// one closer to HEAD
189187
want := "main@sha1:" + test.CommitIDs[3]
190-
if updated.Status.LatestCommit != want {
191-
t.Errorf("failed to update with latest commit, got %q, want %q", updated.Status.LatestCommit, want)
188+
if deployer.Status.LatestCommit != want {
189+
t.Errorf("failed to update with latest commit, got %q, want %q", deployer.Status.LatestCommit, want)
192190
}
193191

194-
updatedRepo := &sourcev1.GitRepository{}
195-
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(repo), updatedRepo))
196-
if updatedRepo.Spec.Reference.Commit != test.CommitIDs[3] {
197-
t.Errorf("failed to configure the GitRepository with the correct commit got %q, want %q", updatedRepo.Spec.Reference.Commit, test.CommitIDs[3])
192+
reload(t, k8sClient, repo)
193+
if repo.Spec.Reference.Commit != test.CommitIDs[3] {
194+
t.Errorf("failed to configure the GitRepository with the correct commit got %q, want %q", repo.Spec.Reference.Commit, test.CommitIDs[3])
198195
}
199196
})
200197

@@ -345,7 +342,8 @@ func TestReconciliation(t *testing.T) {
345342
{
346343
Name: "accessing a test server",
347344
HealthCheck: &deployerv1.HealthCheck{
348-
URL: ts.URL,
345+
URL: ts.URL,
346+
Interval: metav1.Duration{Duration: time.Minute * 7},
349347
},
350348
},
351349
}
@@ -382,16 +380,79 @@ func TestReconciliation(t *testing.T) {
382380
t.Errorf("failed to update with latest commit, got %q, want %q", deployer.Status.LatestCommit, want)
383381
}
384382

383+
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(repo), repo))
384+
if repo.Spec.Reference.Commit != test.CommitIDs[3] {
385+
t.Errorf("failed to configure the GitRepository with the correct commit got %q, want %q", repo.Spec.Reference.Commit, test.CommitIDs[3])
386+
}
387+
388+
assertDeployerGatesEqual(t, deployer, map[string]map[string]bool{
389+
"accessing a test server": {"HealthCheckGate": true},
390+
})
391+
})
392+
393+
t.Run("reconciling with closed gates", func(t *testing.T) {
394+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
395+
http.Error(w, "gate is closed", http.StatusInternalServerError)
396+
}))
397+
t.Cleanup(ts.Close)
398+
399+
ctx := log.IntoContext(context.TODO(), testr.New(t))
400+
deployer := test.NewKustomizationAutoDeployer(func(kd *deployerv1.KustomizationAutoDeployer) {
401+
kd.Spec.Gates = []deployerv1.KustomizationGate{
402+
{
403+
Name: "accessing a closed test server",
404+
HealthCheck: &deployerv1.HealthCheck{
405+
URL: ts.URL,
406+
Interval: metav1.Duration{Duration: time.Minute * 13},
407+
},
408+
},
409+
}
410+
})
411+
412+
test.AssertNoError(t, k8sClient.Create(ctx, deployer))
413+
defer cleanupResource(t, k8sClient, deployer)
414+
415+
// both use the same commit IDs test.CommitIDs[4]
416+
repo := test.NewGitRepository()
417+
test.AssertNoError(t, k8sClient.Create(ctx, repo))
418+
defer cleanupResource(t, k8sClient, repo)
419+
420+
test.UpdateRepoStatus(t, k8sClient, repo, func(r *sourcev1.GitRepository) {
421+
r.Status.Artifact = &sourcev1.Artifact{
422+
Revision: "main@sha1:" + test.CommitIDs[4],
423+
}
424+
})
425+
426+
kustomization := test.NewKustomization(repo)
427+
test.AssertNoError(t, k8sClient.Create(ctx, kustomization))
428+
defer cleanupResource(t, k8sClient, kustomization)
429+
kustomization.Status.LastAppliedRevision = "main@sha1:" + test.CommitIDs[4]
430+
test.AssertNoError(t, k8sClient.Status().Update(ctx, kustomization))
431+
432+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(deployer)})
433+
test.AssertNoError(t, err)
434+
435+
reload(t, k8sClient, deployer)
436+
437+
if res.RequeueAfter != time.Minute*13 {
438+
t.Errorf("failed to set the RequeuAfter from the HealthCheck, got %v, want %v", res.RequeueAfter, time.Minute*13)
439+
}
440+
441+
if deployer.Status.LatestCommit != "" {
442+
t.Errorf("Status.LatestCommit has been populated with %s, it should be empty", deployer.Status.LatestCommit)
443+
}
444+
385445
updatedRepo := &sourcev1.GitRepository{}
386446
test.AssertNoError(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(repo), updatedRepo))
387-
if updatedRepo.Spec.Reference.Commit != test.CommitIDs[3] {
388-
t.Errorf("failed to configure the GitRepository with the correct commit got %q, want %q", updatedRepo.Spec.Reference.Commit, test.CommitIDs[3])
447+
if diff := cmp.Diff(repo.Spec.Reference, updatedRepo.Spec.Reference); diff != "" {
448+
t.Errorf("GitRepository reference has been updated when gates are closed:\n%s", diff)
389449
}
390450

391451
assertDeployerGatesEqual(t, deployer, map[string]map[string]bool{
392-
"accessing a test server": {"HealthCheckGate": true},
452+
"accessing a closed test server": {"HealthCheckGate": false},
393453
})
394454
})
455+
395456
}
396457

397458
func cleanupResource(t *testing.T, cl client.Client, obj client.Object) {
@@ -432,3 +493,7 @@ func assertDeployerGatesEqual(t *testing.T, deployer *deployerv1.KustomizationAu
432493
t.Fatalf("deployer gates do not match:\n%s", diff)
433494
}
434495
}
496+
497+
func reload(t *testing.T, k8sClient client.Client, obj client.Object) {
498+
test.AssertNoError(t, k8sClient.Get(context.TODO(), client.ObjectKeyFromObject(obj), obj))
499+
}

0 commit comments

Comments
 (0)