Skip to content

Commit 6eb043c

Browse files
authored
fix: prevent status update loop (#97)
1 parent f887e54 commit 6eb043c

18 files changed

Lines changed: 103 additions & 58 deletions

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Did you read the contributing and development guides?
99
* http://moolen.github.io/harbor-sync/docs/development
1010
1111
Do the tests pass locally?
12-
* run: <make docker-test> to be sure that all tests pass
12+
* run: <make test> to be sure that all tests pass
1313
1414
If this is a PR is a new feature please provide details and documentation about how to use it.
1515

.github/workflows/e2e.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ jobs:
2828
runs-on: ubuntu-latest
2929
strategy:
3030
matrix:
31-
harbor_version: [v2.2.0, v2.0.6]
31+
include:
32+
- harbor_version: v2.3.1
33+
harbor_chart_version: v1.7.1
34+
- harbor_version: v2.2.0
35+
harbor_chart_version: v1.6.0
36+
- harbor_version: v2.0.6
37+
harbor_chart_version: v1.4.6
3238
steps:
3339
- name: Checkout secret-manger
3440
uses: actions/checkout@v2
@@ -37,13 +43,14 @@ jobs:
3743
- name: Create kind cluster
3844
uses: helm/kind-action@v1.0.0
3945
with:
40-
version: v0.10.0
46+
version: v0.11.1
4147
node_image: kindest/node:v1.20.2
4248
cluster_name: harbor-sync
4349
- name: Run e2e tests
4450
timeout-minutes: 30
4551
env:
4652
HARBOR_VERSION: ${{ matrix.harbor_version }}
53+
HARBOR_CHART_VERSION: ${{ matrix.harbor_chart_version }}
4754
KIND_CLUSTER_NAME: harbor-sync
4855
run: |
4956
export PATH=$PATH:$(go env GOPATH)/bin

Makefile

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ vet:
7474
generate: bin/controller-gen
7575
$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..."
7676

77-
# Run tests in container
78-
docker-test:
79-
rm -rf bin
80-
docker build -t test:latest -f Dockerfile.test .
81-
docker run test:latest
82-
8377
# Build the docker image
8478
docker-build:
8579
docker build . -t ${IMG}

api/v1/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ type WebhookUpdatePayload struct {
102102

103103
// HarborSyncStatus defines the observed state of HarborSync
104104
type HarborSyncStatus struct {
105+
// +optional
106+
LastReconciliation metav1.Time `json:"lastReconciliation,omitempty"`
107+
105108
// +optional
106109
ProjectList []ProjectStatus `json:"projectStatus,omitempty"`
107110

api/v1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func init() {
5656
flags.Duration("harbor-poll-interval", time.Minute*5, "poll interval to update harbor projects & robot accounts")
5757
flags.Duration("force-sync-interval", time.Minute*10, "set this to force reconciliation after a certain time")
5858
flags.Duration("rotation-interval", time.Minute*60, "set this to rotate the credentials after the specified time")
59+
flags.Duration("requeue-interval", time.Minute*5, "set this to prevent reconciling for a specified time")
5960
flags.Bool("leader-elect", true, "enable leader election")
6061
flags.String("namespace", "kube-system", "namespace in which harbor-sync runs (used for leader-election)")
6162
viper.BindPFlags(flags)
@@ -70,6 +71,7 @@ func init() {
7071
viper.BindEnv("harbor-poll-interval", "HARBOR_POLL_INTERVAL")
7172
viper.BindEnv("force-sync-interval", "FORCE_SYNC_INTERVAL")
7273
viper.BindEnv("rotation-interval", "ROTATION_INTERVAL")
74+
viper.BindEnv("requeue-interval", "REQUEUE_INTERVAL")
7375
rootCmd.AddCommand(controllerCmd)
7476
}
7577

@@ -86,6 +88,7 @@ var controllerCmd = &cobra.Command{
8688
"namespace": viper.GetDuration("namespace"),
8789
"force-sync-interval": viper.GetDuration("force-sync-interval"),
8890
"rotation-interval": viper.GetDuration("rotation-interval"),
91+
"requeue-interval": viper.GetDuration("requeue-interval"),
8992
"harbor-poll-interval": viper.GetDuration("harbor-poll-interval"),
9093
"skip-tls-verification": viper.GetDuration("skip-tls-verification"),
9194
"harbor-api-debug": viper.GetDuration("harbor-api-debug"),
@@ -156,6 +159,7 @@ var controllerCmd = &cobra.Command{
156159
CredCache: crdStore,
157160
RotationInterval: viper.GetDuration("rotation-interval"),
158161
Client: mgr.GetClient(),
162+
RequeueInterval: viper.GetDuration("requeue-interval"),
159163
Harbor: harborRepo,
160164
}).SetupWithManager(mgr, syncCfgChanges); err != nil {
161165
log.Error(err, "unable to create controller")

config/crd/bases/crd.harborsync.io_harborsyncs.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ spec:
127127
- type
128128
type: object
129129
type: array
130+
lastReconciliation:
131+
format: date-time
132+
type: string
130133
projectStatus:
131134
items:
132135
properties:

pkg/controllers/controller.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
log "github.com/sirupsen/logrus"
3030
v1 "k8s.io/api/core/v1"
3131
apierrs "k8s.io/apimachinery/pkg/api/errors"
32+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233
ctrl "sigs.k8s.io/controller-runtime"
3334
"sigs.k8s.io/controller-runtime/pkg/client"
3435
"sigs.k8s.io/controller-runtime/pkg/event"
@@ -44,6 +45,7 @@ import (
4445
type HarborSyncConfigReconciler struct {
4546
client.Client
4647
RotationInterval time.Duration
48+
RequeueInterval time.Duration
4749
CredCache reconciler.CredentialStore
4850
Harbor harbor.API
4951
}
@@ -66,8 +68,22 @@ func (r *HarborSyncConfigReconciler) Reconcile(ctx context.Context, req ctrl.Req
6668
return ctrl.Result{}, nil
6769
}
6870
log.Error(err, "unable to fetch sync config")
69-
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 15}, err
71+
return ctrl.Result{RequeueAfter: time.Second * 15}, err
7072
}
73+
74+
defer func() {
75+
err := r.Status().Update(context.Background(), &syncConfig)
76+
if err != nil {
77+
log.Errorf("unable to update status: %s", err.Error())
78+
}
79+
}()
80+
81+
// return early if cr has been updated recently
82+
if !shouldReconcile(syncConfig) {
83+
log.Infof("skipping reconciliation")
84+
return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
85+
}
86+
7187
// mappingFunc calls the Kubernetes-specific mapping functions
7288
mappingFunc := func(
7389
mapping crdv1.ProjectMapping,
@@ -83,12 +99,8 @@ func (r *HarborSyncConfigReconciler) Reconcile(ctx context.Context, req ctrl.Req
8399
err = f(r, mapping, syncConfig, project, *credential, baseURL)
84100
if err != nil {
85101
c := NewSyncCondition(crdv1.HarborSyncReady, v1.ConditionFalse, "Mapping failed", err.Error())
86-
SetSyncCondition(&syncConfig.Status, *c)
87-
err = r.Status().Update(context.Background(), &syncConfig)
88-
if err != nil {
89-
log.Errorf("unable to update status mapping func: %s", err.Error())
90-
}
91102
log.Error(err, "mapping failed")
103+
SetSyncCondition(&syncConfig.Status, *c)
92104
return
93105
}
94106
}
@@ -97,18 +109,11 @@ func (r *HarborSyncConfigReconciler) Reconcile(ctx context.Context, req ctrl.Req
97109
log.Error(err)
98110
c := NewSyncCondition(crdv1.HarborSyncReady, v1.ConditionFalse, "Error Reconciling", err.Error())
99111
SetSyncCondition(&syncConfig.Status, *c)
100-
err = r.Status().Update(context.Background(), &syncConfig)
101-
if err != nil {
102-
log.Errorf("unable to update status after reconcile error: %s", err.Error())
103-
}
104-
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 30}, nil
112+
return ctrl.Result{RequeueAfter: time.Second * 30}, nil
105113
}
106114
c := NewSyncCondition(crdv1.HarborSyncReady, v1.ConditionTrue, "Successfully reconciled", "Successfully reconciled")
115+
syncConfig.Status.LastReconciliation = metav1.Now()
107116
SetSyncCondition(&syncConfig.Status, *c)
108-
err = r.Status().Update(context.Background(), &syncConfig)
109-
if err != nil {
110-
log.Errorf("unable to update status after reconcile: %s", err.Error())
111-
}
112117
log.Info("successfully reconciled")
113118
return ctrl.Result{}, nil
114119
}
@@ -153,6 +158,9 @@ func Reconcile(
153158
selector.ProjectName,
154159
).Set(float64(len(matches)))
155160

161+
// reset projectList
162+
cfg.Status.ProjectList = []crdv1.ProjectStatus{}
163+
156164
// reconcile robot accounts
157165
for _, project := range matches {
158166
credential, changed, err := reconciler.ReconcileRobotAccounts(
@@ -259,3 +267,11 @@ func runWebhook(
259267
}
260268
return fmt.Errorf("webhook errors: %#v", errs)
261269
}
270+
271+
func shouldReconcile(syncConfig crdv1.HarborSync) bool {
272+
cmp := syncConfig.Status.LastReconciliation.Time.Add(time.Minute)
273+
if cmp.After(time.Now()) {
274+
return false
275+
}
276+
return true
277+
}

pkg/controllers/controller_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
. "github.com/onsi/gomega"
3434
v1 "k8s.io/api/core/v1"
3535
"k8s.io/apimachinery/pkg/api/errors"
36+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3637
"k8s.io/apimachinery/pkg/types"
3738
ctrl "sigs.k8s.io/controller-runtime"
3839
)
@@ -51,6 +52,7 @@ var _ = Describe("Controller", func() {
5152
hscr = &HarborSyncConfigReconciler{
5253
k8sClient,
5354
time.Hour * 24,
55+
time.Second * 15,
5456
credStore,
5557
fakeHarbor,
5658
}
@@ -350,3 +352,27 @@ var _ = Describe("Controller", func() {
350352
})
351353
})
352354
})
355+
356+
var _ = Describe("Reconciler", func() {
357+
It("should not reconcile when recently changed", func() {
358+
Expect(shouldReconcile(crdv1.HarborSync{
359+
Status: crdv1.HarborSyncStatus{
360+
LastReconciliation: metav1.Now(),
361+
},
362+
})).To(BeFalse())
363+
})
364+
365+
It("should reconcile when not recently changed", func() {
366+
Expect(shouldReconcile(crdv1.HarborSync{
367+
Status: crdv1.HarborSyncStatus{
368+
LastReconciliation: metav1.NewTime(time.Now().Add(-time.Hour)),
369+
},
370+
})).To(BeTrue())
371+
})
372+
373+
It("should reconcile when status is empty", func() {
374+
Expect(shouldReconcile(crdv1.HarborSync{
375+
Status: crdv1.HarborSyncStatus{},
376+
})).To(BeTrue())
377+
})
378+
})

pkg/controllers/suite_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"k8s.io/client-go/rest"
2929
"sigs.k8s.io/controller-runtime/pkg/client"
3030
"sigs.k8s.io/controller-runtime/pkg/envtest"
31-
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
3231
logf "sigs.k8s.io/controller-runtime/pkg/log"
3332
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3433
// +kubebuilder:scaffold:imports
@@ -41,9 +40,7 @@ var testEnv *envtest.Environment
4140
func TestAPIs(t *testing.T) {
4241
RegisterFailHandler(Fail)
4342

44-
RunSpecsWithDefaultAndCustomReporters(t,
45-
"Controller Suite",
46-
[]Reporter{printer.NewlineReporter{}})
43+
RunSpecs(t, "Controller Suite")
4744
}
4845

4946
var _ = BeforeSuite(func(done Done) {

0 commit comments

Comments
 (0)