Skip to content

Commit

Permalink
feat(#273): reconcile all resources from promise label (#274)
Browse files Browse the repository at this point in the history
* feat(#273): reconcile all resources from promise label

- introduce label 'kratix.io/reconcile-resources'; when set
to true on a promise, kratix will re reconcile all its resources
label will be automatically removed after kratix has set manual
reconciliation label on resources
* chore: extract a configureworkflow failed reason

---------

Co-authored-by: Rich Barton-Cooper <[email protected]>
  • Loading branch information
ChunyiLyu and richcooper95 authored Nov 12, 2024
1 parent 66a6077 commit 898312b
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 21 deletions.
9 changes: 4 additions & 5 deletions controllers/dynamic_resource_request_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ var _ = Describe("DynamicResourceRequestController", func() {
CRD: rrCRD,
PromiseIdentifier: promise.GetName(),
PromiseDestinationSelectors: promise.Spec.DestinationSelectors,
// promiseWorkflowSelectors: work.GetDefaultScheduling("promise-workflow"),
Log: l,
UID: "1234abcd",
Enabled: &enabled,
Log: l,
UID: "1234abcd",
Enabled: &enabled,
}

yamlFile, err := os.ReadFile(resourceRequestPath)
Expand Down Expand Up @@ -221,7 +220,7 @@ var _ = Describe("DynamicResourceRequestController", func() {
))
})

It("re-reconciles until completetion", func() {
It("re-reconciles until completion", func() {
Expect(fakeK8sClient.Delete(ctx, resReq)).To(Succeed())
_, err := t.reconcileUntilCompletion(reconciler, resReq)

Expand Down
36 changes: 29 additions & 7 deletions controllers/promise_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,16 @@ func (r *PromiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct

logger.Info("requirements are fulfilled", "requirementsStatus", promise.Status.RequiredPromises)

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
}
if reconcilesResources(promise) {
logger.Info("reconciling all resource requests of promise", "promiseName", promise.Name)
if err = r.reconcileAllRRs(rrGVK); err != nil {
return ctrl.Result{}, err
}

if _, ok := promise.Labels[resourceutil.ReconcileResourcesLabel]; ok {
return ctrl.Result{}, r.removeReconcileResourcesLabel(ctx, promise)
}

logger.Info("updating observed generation", "from", promise.Status.ObservedGeneration, "to", promise.GetGeneration())
promise.Status.ObservedGeneration = promise.GetGeneration()
return r.updatePromiseStatus(ctx, promise)
Expand Down Expand Up @@ -304,6 +307,25 @@ func (r *PromiseReconciler) hasPromiseRequirementsChanged(ctx context.Context, p
return conditionsFieldChanged || requirementsFieldChanged
}

func reconcilesResources(promise *v1alpha1.Promise) bool {
if promise.Labels != nil && promise.Labels[resourceutil.ReconcileResourcesLabel] == "true" {
return true
}

if promise.GetGeneration() != promise.Status.ObservedGeneration && promise.GetGeneration() != 1 {
return true
}
return false
}

func (r *PromiseReconciler) removeReconcileResourcesLabel(ctx context.Context, promise *v1alpha1.Promise) error {
delete(promise.Labels, resourceutil.ReconcileResourcesLabel)
if err := r.Client.Update(ctx, promise); err != nil {
return err
}
return nil
}

func updateConditionOnPromise(promise *v1alpha1.Promise, latestCondition metav1.Condition) bool {
for i, condition := range promise.Status.Conditions {
if condition.Type == latestCondition.Type {
Expand Down Expand Up @@ -463,7 +485,7 @@ func (r *PromiseReconciler) reconcileAllRRs(rrGVK schema.GroupVersionKind) error
}
newLabels[resourceutil.ManualReconciliationLabel] = "true"
rr.SetLabels(newLabels)
if err := r.Client.Update(context.TODO(), &rr); err != nil {
if err := r.Client.Update(context.Background(), &rr); err != nil {
return err
}
}
Expand Down
56 changes: 56 additions & 0 deletions controllers/promise_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"k8s.io/apimachinery/pkg/runtime/schema"
"os"
"regexp"
"strings"
Expand Down Expand Up @@ -912,6 +913,61 @@ var _ = Describe("PromiseController", func() {
})
})
})

When("promise labeled with ReconcileResources label", func() {
var resReq *unstructured.Unstructured

BeforeEach(func() {
promise = createPromise(promiseWithWorkflowPath)
setReconcileConfigureWorkflowToReturnFinished()
_, err := t.reconcileUntilCompletion(reconciler, promise, &opts{
funcs: []func(client.Object) error{autoMarkCRDAsEstablished},
})
Expect(err).NotTo(HaveOccurred())

resReqBytes, err := os.ReadFile(resourceRequestPath)
Expect(err).ToNot(HaveOccurred())

resReq = &unstructured.Unstructured{}
Expect(yaml.Unmarshal(resReqBytes, resReq)).To(Succeed())
Expect(fakeK8sClient.Create(ctx, resReq)).To(Succeed())

Expect(fakeK8sClient.Get(ctx, types.NamespacedName{
Name: resReq.GetName(),
Namespace: resReq.GetNamespace(),
}, resReq)).To(Succeed())
})

It("re runs all resource configure workflows", func() {
Expect(fakeK8sClient.Get(ctx, promiseName, promise)).To(Succeed())
promise.Labels[resourceutil.ReconcileResourcesLabel] = "true"
Expect(fakeK8sClient.Update(context.TODO(), promise)).To(Succeed())

_, err := t.reconcileUntilCompletion(reconciler, promise, &opts{
funcs: []func(client.Object) error{autoMarkCRDAsEstablished},
})
Expect(err).NotTo(HaveOccurred())

By("labelling manual reconciliation on resources", func() {
updatedRR := &unstructured.Unstructured{}
updatedRR.SetGroupVersionKind(schema.GroupVersionKind{
Group: "marketplace.kratix.io",
Version: "v1alpha1",
Kind: "redis",
})
Expect(fakeK8sClient.Get(ctx, types.NamespacedName{
Name: resReq.GetName(),
Namespace: resReq.GetNamespace(),
}, updatedRR)).To(Succeed())
Expect(updatedRR.GetLabels()).To(HaveKeyWithValue(resourceutil.ManualReconciliationLabel, "true"))
})

By("removing ReconcileResources label from promise", func() {
Expect(fakeK8sClient.Get(ctx, promiseName, promise)).To(Succeed())
Expect(promise.Labels).NotTo(HaveKey(resourceutil.ReconcileResourcesLabel))
})
})
})
})

Describe("Promise API", func() {
Expand Down
20 changes: 11 additions & 9 deletions lib/resourceutil/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import (
)

const (
ConfigureWorkflowCompletedCondition = clusterv1.ConditionType("ConfigureWorkflowCompleted")
ManualReconciliationLabel = "kratix.io/manual-reconciliation"
promiseAvailableCondition = clusterv1.ConditionType("PromiseAvailable")
promiseRequirementsNotMetReason = "PromiseRequirementsNotInstalled"
promiseRequirementsNotMetMessage = "Promise Requirements are not installed"
promiseRequirementsMetReason = "PromiseAvailable"
promiseRequirementsMetMessage = "Promise Requirements are met"
ConfigureWorkflowCompletedCondition = clusterv1.ConditionType("ConfigureWorkflowCompleted")
ConfigureWorkflowCompletedFailedReason = "ConfigureWorkflowFailed"
ManualReconciliationLabel = "kratix.io/manual-reconciliation"
ReconcileResourcesLabel = "kratix.io/reconcile-resources"
promiseAvailableCondition = clusterv1.ConditionType("PromiseAvailable")
promiseRequirementsNotMetReason = "PromiseRequirementsNotInstalled"
promiseRequirementsNotMetMessage = "Promise Requirements are not installed"
promiseRequirementsMetReason = "PromiseAvailable"
promiseRequirementsMetMessage = "Promise Requirements are met"
)

func GetConfigureWorkflowCompletedConditionStatus(obj *unstructured.Unstructured) v1.ConditionStatus {
Expand All @@ -49,10 +51,10 @@ func MarkWorkflowAsFailed(logger logr.Logger, obj *unstructured.Unstructured, fa
Type: ConfigureWorkflowCompletedCondition,
Status: v1.ConditionFalse,
Message: fmt.Sprintf("A Pipeline has failed: %s", failedPipeline),
Reason: "ConfigureWorkflowFailed",
Reason: ConfigureWorkflowCompletedFailedReason,
LastTransitionTime: metav1.NewTime(time.Now()),
})
logger.Info("set conditions", "condition", ConfigureWorkflowCompletedCondition, "value", v1.ConditionFalse, "reason", "ConfigureWorkflowFailed")
logger.Info("set conditions", "condition", ConfigureWorkflowCompletedCondition, "value", v1.ConditionFalse, "reason", ConfigureWorkflowCompletedFailedReason)
}

func SortJobsByCreationDateTime(jobs []batchv1.Job, desc bool) []batchv1.Job {
Expand Down

0 comments on commit 898312b

Please sign in to comment.