From bca35ff536c8f34ac8eaa9a9cb742ffc0d3caf59 Mon Sep 17 00:00:00 2001 From: Abby Bangser Date: Tue, 15 Oct 2024 10:24:50 +0100 Subject: [PATCH] feat: Run Promise configure workflows at least every 10 hours This is the first of a handful of features which will combine to provide regular reconciliation across the full Kratix platform. Right now the reconcilation time is set to 10 hours but will be configurable in the future. There are two notes here to be aware of: 1. If the kratix controller restarts, there may be *more* reconciliation loops scheduled. That is why this is saying "at least" every 10 hours rather than "at most" or "exactly". 2. The workflows will run, but if the declarative outputs are not changed, the write to the statestore will not be triggered. Making sure to reconcile the actual contents of the statestore with the declarede contents is an upcoming piece of work to in this stream. closes #219 Co-authored-by: Sapphire Mason-Brown Co-authored-by: Derik Evangelista Co-authored-by: Rich Barton-Cooper --- api/v1alpha1/promise_types.go | 10 ++ api/v1alpha1/zz_generated.deepcopy.go | 4 + .../bases/platform.kratix.io_promises.yaml | 3 + controllers/assets/promise-with-workflow.yaml | 2 + controllers/promise_controller.go | 83 ++++++++--- controllers/promise_controller_test.go | 129 ++++++++++++++++-- controllers/scheduler.go | 2 + controllers/scheduler_test.go | 13 ++ controllers/shared.go | 2 + controllers/shared_test.go | 9 +- go.mod | 28 ++-- go.sum | 28 ++++ main.go | 14 +- 13 files changed, 275 insertions(+), 52 deletions(-) diff --git a/api/v1alpha1/promise_types.go b/api/v1alpha1/promise_types.go index b4abd973..5deb47ac 100644 --- a/api/v1alpha1/promise_types.go +++ b/api/v1alpha1/promise_types.go @@ -118,6 +118,7 @@ type PromiseStatus struct { Status string `json:"status,omitempty"` RequiredPromises []RequiredPromiseStatus `json:"requiredPromises,omitempty"` RequiredBy []RequiredBy `json:"requiredBy,omitempty"` + LastAvailableTime *metav1.Time `json:"lastAvailableTime,omitempty"` } type PromiseSummary struct { @@ -252,6 +253,15 @@ func (d Dependencies) Marshal() ([]byte, error) { return io.ReadAll(buf) } +func (p *Promise) GetCondition(conditionType string) *metav1.Condition { + for i := range p.Status.Conditions { + if p.Status.Conditions[i].Type == conditionType { + return &p.Status.Conditions[i] + } + } + return nil +} + //+kubebuilder:object:root=true // PromiseList contains a list of Promise diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 25d4638d..1da54e08 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -758,6 +758,10 @@ func (in *PromiseStatus) DeepCopyInto(out *PromiseStatus) { *out = make([]RequiredBy, len(*in)) copy(*out, *in) } + if in.LastAvailableTime != nil { + in, out := &in.LastAvailableTime, &out.LastAvailableTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PromiseStatus. diff --git a/config/crd/bases/platform.kratix.io_promises.yaml b/config/crd/bases/platform.kratix.io_promises.yaml index 213743a6..ba7dea88 100644 --- a/config/crd/bases/platform.kratix.io_promises.yaml +++ b/config/crd/bases/platform.kratix.io_promises.yaml @@ -192,6 +192,9 @@ spec: type: array kind: type: string + lastAvailableTime: + format: date-time + type: string observedGeneration: format: int64 type: integer diff --git a/controllers/assets/promise-with-workflow.yaml b/controllers/assets/promise-with-workflow.yaml index 315904fd..e48c3008 100644 --- a/controllers/assets/promise-with-workflow.yaml +++ b/controllers/assets/promise-with-workflow.yaml @@ -2,6 +2,8 @@ apiVersion: platform.kratix.io/v1alpha1 kind: Promise metadata: name: promise-with-workflow + labels: + kratix.io/promise-version: v1.1.0 spec: api: apiVersion: apiextensions.k8s.io/v1 diff --git a/controllers/promise_controller.go b/controllers/promise_controller.go index 7b4d38e9..8b45ac3e 100644 --- a/controllers/promise_controller.go +++ b/controllers/promise_controller.go @@ -21,6 +21,7 @@ import ( "fmt" "slices" "strings" + "sync" "time" "github.com/syntasso/kratix/lib/objectutil" @@ -69,6 +70,10 @@ type PromiseReconciler struct { StartedDynamicControllers map[string]*DynamicResourceRequestController RestartManager func() NumberOfJobsToKeep int + + ScheduledReconciliation map[string]metav1.Time + + mutex sync.Mutex } const ( @@ -77,6 +82,7 @@ const ( dynamicControllerDependantResourcesCleanupFinalizer = v1alpha1.KratixPrefix + "dynamic-controller-dependant-resources-cleanup" crdCleanupFinalizer = v1alpha1.KratixPrefix + "api-crd-cleanup" dependenciesCleanupFinalizer = v1alpha1.KratixPrefix + "dependencies-cleanup" + lastUpdatedAtAnnotation = v1alpha1.KratixPrefix + "last-updated-at" requirementStateInstalled = "Requirement installed" requirementStateNotInstalled = "Requirement not installed" @@ -95,9 +101,9 @@ var ( // fastRequeue can be used whenever we want to quickly requeue, and we don't expect // an error to occur. Example: we delete a resource, we then requeue // to check it's been deleted. Here we can use a fastRequeue instead of a defaultRequeue - fastRequeue = ctrl.Result{RequeueAfter: 1 * time.Second} - defaultRequeue = ctrl.Result{RequeueAfter: 5 * time.Second} - slowRequeue = ctrl.Result{RequeueAfter: 15 * time.Second} + fastRequeue = ctrl.Result{RequeueAfter: 5 * time.Second} + defaultRequeue = ctrl.Result{RequeueAfter: 15 * time.Second} + slowRequeue = ctrl.Result{RequeueAfter: 60 * time.Second} ) //+kubebuilder:rbac:groups=platform.kratix.io,resources=promises,verbs=get;list;watch;create;update;patch;delete @@ -152,9 +158,16 @@ func (r *PromiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct //Set status to unavailable, at the end of this function we set it to //available. If at anytime we return early, it persisted as unavailable promise.Status.Status = v1alpha1.PromiseStatusUnavailable - updated, err := r.ensureRequiredPromiseStatusIsUpToDate(ctx, promise) - if err != nil || updated { - return ctrl.Result{}, err + requirementsChanged := r.hasPromiseRequirementsChanged(ctx, promise) + + scheduledReconciliation := promise.Status.LastAvailableTime != nil && time.Since(promise.Status.LastAvailableTime.Time) > DefaultReconciliationInterval + if (requirementsChanged || scheduledReconciliation) && originalStatus == v1alpha1.PromiseStatusAvailable { + err := r.Client.Status().Update(ctx, promise) + if err != nil { + return ctrl.Result{}, err + } + logger.Info("Requeueing: requirements changed or scheduled reconciliation") + return ctrl.Result{}, nil } //TODO handle removing finalizer @@ -204,13 +217,14 @@ func (r *PromiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return addFinalizers(opts, promise, []string{dependenciesCleanupFinalizer}) } - requeue, err = r.reconcileDependenciesAndPromiseWorkflows(opts, promise) + ctrlResult, err := r.reconcileDependenciesAndPromiseWorkflows(opts, promise) if err != nil { return ctrl.Result{}, err } - if requeue != nil { - return *requeue, nil + if ctrlResult != nil { + logger.Info("stopping reconciliation while reconciling dependencies") + return *ctrlResult, nil } if promise.ContainsAPI() { @@ -240,10 +254,12 @@ func (r *PromiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct if promise.GetGeneration() != promise.Status.ObservedGeneration { if promise.GetGeneration() != 1 { + logger.Info("reconciling all RRs") if err := r.reconcileAllRRs(rrGVK); err != nil { return ctrl.Result{}, err } } + logger.Info("updating observed generation", "from", promise.Status.ObservedGeneration, "to", promise.GetGeneration()) promise.Status.ObservedGeneration = promise.GetGeneration() return ctrl.Result{}, r.Client.Status().Update(ctx, promise) } @@ -252,24 +268,38 @@ func (r *PromiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } if originalStatus == v1alpha1.PromiseStatusAvailable { - return ctrl.Result{}, nil + return r.nextReconciliation(promise, logger) } + logger.Info("Promise status being set to Available") promise.Status.Status = v1alpha1.PromiseStatusAvailable + promise.Status.LastAvailableTime = &metav1.Time{Time: time.Now()} return ctrl.Result{}, r.Client.Status().Update(ctx, promise) } -func (r *PromiseReconciler) ensureRequiredPromiseStatusIsUpToDate(ctx context.Context, promise *v1alpha1.Promise) (bool, error) { +func (r *PromiseReconciler) nextReconciliation(promise *v1alpha1.Promise, logger logr.Logger) (ctrl.Result, error) { + r.mutex.Lock() + defer r.mutex.Unlock() + + scheduled, found := r.ScheduledReconciliation[promise.GetName()] + if !found || time.Now().After(scheduled.Time) { + next := metav1.NewTime(time.Now().Add(DefaultReconciliationInterval)) + r.ScheduledReconciliation[promise.GetName()] = next + + logger.Info("Scheduling next reconciliation", "scheduledReconciliationTimestamp", next.Time.String()) + return ctrl.Result{RequeueAfter: DefaultReconciliationInterval}, nil + } + logger.Info("Reconciliation already scheduled", "scheduledReconciliationTimestamp", scheduled.Time.String(), "labels", promise.Labels) + return ctrl.Result{}, nil +} + +func (r *PromiseReconciler) hasPromiseRequirementsChanged(ctx context.Context, promise *v1alpha1.Promise) bool { latestCondition, latestRequirements := r.generateStatusAndMarkRequirements(ctx, promise) requirementsFieldChanged := updateRequirementsStatusOnPromise(promise, promise.Status.RequiredPromises, latestRequirements) conditionsFieldChanged := updateConditionOnPromise(promise, latestCondition) - if conditionsFieldChanged || requirementsFieldChanged { - return true, r.Client.Status().Update(ctx, promise) - } - - return false, nil + return conditionsFieldChanged || requirementsFieldChanged } func updateConditionOnPromise(promise *v1alpha1.Promise, latestCondition metav1.Condition) bool { @@ -377,6 +407,19 @@ func (r *PromiseReconciler) reconcileDependenciesAndPromiseWorkflows(o opts, pro } o.logger.Info("Promise contains workflows.promise.configure, reconciling workflows") + pipelineCompletedCondition := promise.GetCondition(string(resourceutil.PipelineCompletedCondition)) + forcePipelineRun := pipelineCompletedCondition != nil && pipelineCompletedCondition.Status == "True" && time.Since(pipelineCompletedCondition.LastTransitionTime.Time) > DefaultReconciliationInterval + if forcePipelineRun { + o.logger.Info("Pipeline completed too long ago... forcing the reconciliation", "lastTransitionTime", pipelineCompletedCondition.LastTransitionTime.Time.String()) + if promise.Labels == nil { + promise.Labels = make(map[string]string) + } + promise.Labels[resourceutil.ManualReconciliationLabel] = "true" + if err := r.Client.Update(o.ctx, promise); err != nil { + return &ctrl.Result{}, err + } + } + unstructuredPromise, err := promise.ToUnstructured() if err != nil { return nil, err @@ -977,6 +1020,14 @@ func (r *PromiseReconciler) applyWorkForStaticDependencies(o opts, promise *v1al } else { op = "updated" existingWork.Spec = work.Spec + + ann := existingWork.GetAnnotations() + if ann == nil { + ann = map[string]string{} + } + ann[lastUpdatedAtAnnotation] = time.Now().Local().String() + existingWork.SetAnnotations(ann) + err = r.Client.Update(o.ctx, existingWork) } diff --git a/controllers/promise_controller_test.go b/controllers/promise_controller_test.go index a023dfbe..05c0ded5 100644 --- a/controllers/promise_controller_test.go +++ b/controllers/promise_controller_test.go @@ -7,6 +7,7 @@ import ( "os" "regexp" "strings" + "time" "github.com/go-logr/logr" . "github.com/onsi/ginkgo/v2" @@ -28,7 +29,9 @@ import ( "github.com/syntasso/kratix/controllers" "github.com/syntasso/kratix/controllers/controllersfakes" "github.com/syntasso/kratix/lib/compression" + "github.com/syntasso/kratix/lib/resourceutil" "github.com/syntasso/kratix/lib/workflow" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) var ( @@ -54,10 +57,11 @@ var _ = Describe("PromiseController", func() { managerRestarted = false l = ctrl.Log.WithName("controllers").WithName("Promise") reconciler = &controllers.PromiseReconciler{ - Client: fakeK8sClient, - ApiextensionsClient: fakeApiExtensionsClient, - Log: l, - Manager: &controllersfakes.FakeManager{}, + Client: fakeK8sClient, + ApiextensionsClient: fakeApiExtensionsClient, + Log: l, + Manager: &controllersfakes.FakeManager{}, + ScheduledReconciliation: map[string]metav1.Time{}, RestartManager: func() { managerRestarted = true }, @@ -77,7 +81,7 @@ var _ = Describe("PromiseController", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) By("creating the CRD", func() { crd, err := fakeApiExtensionsClient.CustomResourceDefinitions().Get(ctx, expectedCRDName, metav1.GetOptions{}) @@ -182,6 +186,7 @@ var _ = Describe("PromiseController", func() { work := getWork("kratix-platform-system", promise.GetName(), "", "") Expect(inCompressedContents(work.Spec.WorkloadGroups[0].Workloads[0].Content, []byte("kind: Deployment"))).To(BeTrue()) Expect(inCompressedContents(work.Spec.WorkloadGroups[0].Workloads[0].Content, []byte("kind: ClusterRoleBinding"))).To(BeTrue()) + Expect(work.GetAnnotations()).To(HaveKey("kratix.io/last-updated-at")) }) @@ -463,7 +468,7 @@ var _ = Describe("PromiseController", func() { result, err := t.reconcileUntilCompletion(reconciler, promise) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) }) By("not creating a Work for the empty static dependencies", func() { @@ -498,7 +503,7 @@ var _ = Describe("PromiseController", func() { It("re-reconciles until completion", func() { result, err := t.reconcileUntilCompletion(reconciler, promise) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) crds, err := fakeApiExtensionsClient.CustomResourceDefinitions().List(ctx, metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -546,7 +551,7 @@ var _ = Describe("PromiseController", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) }) It("sets the finalizers on the Promise", func() { @@ -658,7 +663,7 @@ var _ = Describe("PromiseController", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) }) It("sets the delete-workflows finalizer", func() { @@ -752,7 +757,7 @@ var _ = Describe("PromiseController", func() { funcs: []func(client.Object) error{autoMarkCRDAsEstablished}, }) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) }) When("it contains static dependencies", func() { @@ -809,6 +814,104 @@ var _ = Describe("PromiseController", func() { }) }) }) + + When("the reconciliation interval is reached", func() { + BeforeEach(func() { + promise = createPromise(promiseWithWorkflowPath) + setReconcileConfigureWorkflowToReturnFinished() + + result, err := t.reconcileUntilCompletion(reconciler, promise, &opts{ + funcs: []func(client.Object) error{autoMarkCRDAsEstablished}, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) + + Expect(fakeK8sClient.Get(ctx, types.NamespacedName{Name: promise.GetName()}, promise)).To(Succeed()) + + uPromise, err := promise.ToUnstructured() + Expect(err).NotTo(HaveOccurred()) + + resourceutil.SetCondition(uPromise, &clusterv1.Condition{ + Type: resourceutil.PipelineCompletedCondition, + Status: v1.ConditionTrue, + Message: "Pipeline completed", + Reason: "PipelineExecutedSuccessfully", + LastTransitionTime: metav1.NewTime(time.Now().Add(-controllers.DefaultReconciliationInterval)), + }) + resourceutil.MarkPipelineAsCompleted(logr.Logger{}, uPromise) + Expect(fakeK8sClient.Status().Update(ctx, uPromise)).To(Succeed()) + }) + + It("re-runs the promise.configure workflow and sets the next reconciliation timestamp", func() { + Expect(fakeK8sClient.Get(ctx, promiseName, promise)).To(Succeed()) + tm := metav1.NewTime(time.Now().Add(-controllers.DefaultReconciliationInterval)) + promise.Status.LastAvailableTime = &tm + Expect(fakeK8sClient.Status().Update(ctx, promise)).To(Succeed()) + + result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: promise.GetName(), Namespace: promise.GetNamespace()}}) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + + Expect(fakeK8sClient.Get(ctx, promiseName, promise)).To(Succeed()) + Expect(promise.Status.Status).To(Equal(v1alpha1.PromiseStatusUnavailable)) + + By("Adding the manual reconciliation label", func() { + result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: promise.GetName(), Namespace: promise.GetNamespace()}}) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + + Expect(fakeK8sClient.Get(ctx, promiseName, promise)).To(Succeed()) + Expect(promise.Labels[resourceutil.ManualReconciliationLabel]).To(Equal("true")) + }) + + By("running the pipelines", func() { + Expect(fakeK8sClient.Get(ctx, types.NamespacedName{Name: promise.GetName()}, promise)).To(Succeed()) + + uPromise, err := promise.ToUnstructured() + Expect(err).NotTo(HaveOccurred()) + + resourceutil.MarkPipelineAsRunning(logr.Logger{}, uPromise) + Expect(fakeK8sClient.Status().Update(ctx, uPromise)).To(Succeed()) + + result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: promise.GetName(), Namespace: promise.GetNamespace()}}) + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + }) + + By("completing the pipelines", func() { + Expect(fakeK8sClient.Get(ctx, types.NamespacedName{Name: promise.GetName()}, promise)).To(Succeed()) + + uPromise, err := promise.ToUnstructured() + Expect(err).NotTo(HaveOccurred()) + + resourceutil.SetCondition(uPromise, &clusterv1.Condition{ + Type: resourceutil.PipelineCompletedCondition, + Status: v1.ConditionTrue, + Message: "Pipeline completed", + Reason: "PipelineExecutedSuccessfully", + LastTransitionTime: metav1.NewTime(time.Now()), + }) + resourceutil.MarkPipelineAsCompleted(logr.Logger{}, uPromise) + Expect(fakeK8sClient.Status().Update(ctx, uPromise)).To(Succeed()) + + Expect(fakeK8sClient.Get(ctx, promiseName, promise)).To(Succeed()) + Expect(promise.Status.Status).To(Equal(v1alpha1.PromiseStatusAvailable)) + }) + + By("setting up the next reconciliation loop", func() { + result, err := t.reconcileUntilCompletion(reconciler, promise, &opts{ + funcs: []func(client.Object) error{autoMarkCRDAsEstablished}, + }) + + Expect(fakeK8sClient.Get(ctx, types.NamespacedName{Name: promise.GetName()}, promise)).To(Succeed()) + + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{})) + Expect(reconciler.ScheduledReconciliation[promise.GetName()].Unix()).To(Equal(time.Now().Add(controllers.DefaultReconciliationInterval).Unix())) + }) + }) + }) }) Describe("Promise API", func() { @@ -820,7 +923,7 @@ var _ = Describe("PromiseController", func() { funcs: []func(client.Object) error{autoMarkCRDAsEstablished}, }) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) crd, err := fakeApiExtensionsClient.CustomResourceDefinitions().Get(ctx, expectedCRDName, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) @@ -857,7 +960,7 @@ var _ = Describe("PromiseController", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(ctrl.Result{})) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: controllers.DefaultReconciliationInterval})) crd, err := fakeApiExtensionsClient.CustomResourceDefinitions().Get(ctx, expectedCRDName, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) @@ -909,6 +1012,8 @@ func getWork(namespace, promiseName, resourceName, pipelineName string) v1alpha1 workSelectorLabel := labels.FormatLabels(l) selector, err := labels.Parse(workSelectorLabel) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + err = fakeK8sClient.List(context.Background(), &works, &client.ListOptions{ LabelSelector: selector, Namespace: namespace, diff --git a/controllers/scheduler.go b/controllers/scheduler.go index 68a52f32..ad59bff7 100644 --- a/controllers/scheduler.go +++ b/controllers/scheduler.go @@ -301,6 +301,8 @@ func (s *Scheduler) applyWorkplacementsForTargetDestinations(workloadGroup v1alp workLabelKey: work.Name, workloadGroupIDKey: workloadGroup.ID, } + workPlacement.SetAnnotations(work.GetAnnotations()) + workPlacement.SetPipelineName(work) if misscheduled { diff --git a/controllers/scheduler_test.go b/controllers/scheduler_test.go index cf4c2d14..a91f1d44 100644 --- a/controllers/scheduler_test.go +++ b/controllers/scheduler_test.go @@ -86,6 +86,7 @@ var _ = Describe("Controllers/Scheduler", func() { Expect(workPlacement.GetLabels()).To(SatisfyAll( HaveKeyWithValue("kratix.io/pipeline-name", resourceWork.Labels["kratix.io/pipeline-name"]), )) + Expect(workPlacement.GetAnnotations()).To(Equal(resourceWork.GetAnnotations())) }) It("sets the scheduling conditions on the Work", func() { @@ -138,6 +139,7 @@ var _ = Describe("Controllers/Scheduler", func() { Expect(newWorkPlacement.Finalizers).To(Equal(workPlacement.Finalizers)) Expect(newWorkPlacement.Spec.PromiseName).To(Equal(workPlacement.Spec.PromiseName)) Expect(newWorkPlacement.Spec.ResourceName).To(Equal(workPlacement.Spec.ResourceName)) + Expect(newWorkPlacement.GetAnnotations()).To(Equal(workPlacement.GetAnnotations())) }) }) @@ -345,6 +347,7 @@ var _ = Describe("Controllers/Scheduler", func() { Expect(devOrProdWorkPlacement.Finalizers[0]).To(Equal("finalizers.workplacement.kratix.io/repo-cleanup")) Expect(devOrProdWorkPlacement.Spec.PromiseName).To(Equal("promise")) Expect(devOrProdWorkPlacement.Spec.ResourceName).To(Equal("resource")) + Expect(devOrProdWorkPlacement.GetAnnotations()).To(Equal(resourceWorkWithMultipleGroups.GetAnnotations())) Expect(pciWorkPlacement.Namespace).To(Equal("default")) Expect(pciWorkPlacement.ObjectMeta.Labels["kratix.io/work"]).To(Equal("rr-work-name-with-two-groups")) @@ -356,6 +359,8 @@ var _ = Describe("Controllers/Scheduler", func() { Expect(pciWorkPlacement.Finalizers[0]).To(Equal("finalizers.workplacement.kratix.io/repo-cleanup")) Expect(pciWorkPlacement.Spec.PromiseName).To(Equal("promise")) Expect(pciWorkPlacement.Spec.ResourceName).To(Equal("resource")) + Expect(pciWorkPlacement.Spec.ResourceName).To(Equal("resource")) + Expect(pciWorkPlacement.GetAnnotations()).To(Equal(resourceWorkWithMultipleGroups.GetAnnotations())) }) }) @@ -434,6 +439,7 @@ var _ = Describe("Controllers/Scheduler", func() { Expect(pciWorkPlacement.Spec.Workloads).To(Equal(resourceWorkWithMultipleGroups.Spec.WorkloadGroups[1].Workloads)) Expect(pciWorkPlacement.Spec.PromiseName).To(Equal("promise")) Expect(pciWorkPlacement.Spec.ResourceName).To(Equal("resource")) + Expect(pciWorkPlacement.GetAnnotations()).To(Equal(resourceWorkWithMultipleGroups.GetAnnotations())) }) It("returns an error indicating what was unschedulable", func() { @@ -554,6 +560,7 @@ var _ = Describe("Controllers/Scheduler", func() { HaveKeyWithValue("kratix.io/pipeline-name", dependencyWorkForProd.Labels["kratix.io/pipeline-name"]), HaveKeyWithValue("kratix.io/work", dependencyWorkForProd.Name), )) + Expect(workPlacements.Items[0].GetAnnotations()).To(Equal(dependencyWorkForProd.GetAnnotations())) }) }) @@ -855,6 +862,9 @@ func newWork(name string, isResource bool, scheduling ...WorkloadGroupScheduling "kratix.io/work": name, "kratix.io/pipeline-name": fmt.Sprintf("workflow-%s", uuid.New().String()[0:8]), }, + Annotations: map[string]string{ + "kratix.io/some-annotation": "some-value", + }, }, Spec: WorkSpec{ PromiseName: "promise", @@ -898,6 +908,9 @@ func newWorkWithTwoWorkloadGroups(name string, isResource bool, promiseSchedulin ObjectMeta: v1.ObjectMeta{ Name: name, Namespace: namespace, + Annotations: map[string]string{ + "kratix.io/annotation-key": "annotation-value", + }, }, Spec: WorkSpec{ PromiseName: "promise", diff --git a/controllers/shared.go b/controllers/shared.go index e55ce30e..40931911 100644 --- a/controllers/shared.go +++ b/controllers/shared.go @@ -3,6 +3,7 @@ package controllers import ( "context" "fmt" + "time" "github.com/go-logr/logr" "github.com/syntasso/kratix/api/v1alpha1" @@ -24,6 +25,7 @@ const ( promiseReleaseNameLabel = v1alpha1.KratixPrefix + "promise-release-name" removeAllWorkflowJobsFinalizer = v1alpha1.KratixPrefix + "workflows-cleanup" runDeleteWorkflowsFinalizer = v1alpha1.KratixPrefix + "delete-workflows" + DefaultReconciliationInterval = time.Hour * 10 ) var ( diff --git a/controllers/shared_test.go b/controllers/shared_test.go index 157d0146..e097dfed 100644 --- a/controllers/shared_test.go +++ b/controllers/shared_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/syntasso/kratix/api/v1alpha1" + "github.com/syntasso/kratix/controllers" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" @@ -75,7 +76,7 @@ type testReconciler struct { } // Run the reconciler until all these are satisfied: -// - reconciler is not returning a requeuing result and no error +// - reconciler is not returning a requeuing result and no error (e.g. `ctrl.Result{}, nil`) // - the resource is not updated after a reconcile // TODO: We watch for various other resources to trigger reconciliaton loop, // e.g. changes to jobs owned by a promise trigger the promise. Need to improve @@ -132,14 +133,14 @@ func (t *testReconciler) reconcileUntilCompletion(r kubebuilder.Reconciler, obj err = fakeK8sClient.Get(context.Background(), namespacedName, newK8sObj) if err != nil { if errors.IsNotFound(err) { - // The object was deleted, so we need to requeue one last time to mimick - // the k8s api behaviou + // The object was deleted, so we need to requeue one last time to mimic + // the k8s api behaviour return r.Reconcile(context.Background(), ctrl.Request{NamespacedName: namespacedName}) } return ctrl.Result{}, err } - if int64(result.RequeueAfter) == 0 && k8sObj.GetResourceVersion() == newK8sObj.GetResourceVersion() { + if (int64(result.RequeueAfter) == 0 || result.RequeueAfter == controllers.DefaultReconciliationInterval) && k8sObj.GetResourceVersion() == newK8sObj.GetResourceVersion() { return result, nil } diff --git a/go.mod b/go.mod index eb7fe0bd..0e38710f 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,15 @@ go 1.22.5 require ( github.com/go-git/go-git/v5 v5.11.0 - github.com/go-logr/logr v1.4.1 + github.com/go-logr/logr v1.4.2 github.com/google/uuid v1.6.0 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 github.com/minio/minio-go/v7 v7.0.68 - github.com/onsi/ginkgo/v2 v2.17.2 - github.com/onsi/gomega v1.33.1 + github.com/onsi/ginkgo/v2 v2.20.2 + github.com/onsi/gomega v1.34.1 github.com/pkg/errors v0.9.1 go.uber.org/zap v1.26.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.30.1 k8s.io/apiextensions-apiserver v0.30.1 @@ -54,7 +54,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -80,19 +80,19 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.24.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 55d9e288..8e397a68 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3c github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -81,6 +83,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -129,8 +133,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -189,14 +197,20 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -210,6 +224,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -219,6 +235,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -236,6 +254,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -243,6 +263,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -253,6 +275,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -263,6 +287,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -273,6 +299,8 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/main.go b/main.go index 70ef3af6..33b184f9 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/go-logr/logr" "github.com/syntasso/kratix/api/v1alpha1" @@ -140,12 +141,13 @@ func main() { restartManager := false restartManagerInProgress := false if err = (&controllers.PromiseReconciler{ - ApiextensionsClient: apiextensionsClient.ApiextensionsV1(), - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Promise"), - Manager: mgr, - Scheme: mgr.GetScheme(), - NumberOfJobsToKeep: getNumJobsToKeep(kratixConfig), + ApiextensionsClient: apiextensionsClient.ApiextensionsV1(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Promise"), + Manager: mgr, + Scheme: mgr.GetScheme(), + NumberOfJobsToKeep: getNumJobsToKeep(kratixConfig), + ScheduledReconciliation: map[string]metav1.Time{}, RestartManager: func() { // This function gets called multiple times // First call: restartInProgress get set to true, sleeps starts