diff --git a/api/v1alpha1/argocd_types.go b/api/v1alpha1/argocd_types.go index bd016e5ca..599711867 100644 --- a/api/v1alpha1/argocd_types.go +++ b/api/v1alpha1/argocd_types.go @@ -837,6 +837,15 @@ type ArgoCDSpec struct { AggregatedClusterRoles bool `json:"aggregatedClusterRoles,omitempty"` } +const ( + ArgoCDConditionType = "Reconciled" +) + +const ( + ArgoCDConditionReasonSuccess = "Success" + ArgoCDConditionReasonErrorOccurred = "ErrorOccurred" +) + // ArgoCDStatus defines the observed state of ArgoCD // +k8s:openapi-gen=true type ArgoCDStatus struct { @@ -920,6 +929,9 @@ type ArgoCDStatus struct { // Host is the hostname of the Ingress. Host string `json:"host,omitempty"` + + // Conditions is an array of the ArgoCD's status conditions + Conditions []metav1.Condition `json:"conditions,omitempty"` } // Banner defines an additional banner message to be displayed in Argo CD UI diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e089557e1..b2c9c5ab2 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -35,7 +35,7 @@ func (in *ArgoCD) DeepCopyInto(out *ArgoCD) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCD. @@ -977,6 +977,13 @@ func (in *ArgoCDSpec) DeepCopy() *ArgoCDSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoCDStatus) DeepCopyInto(out *ArgoCDStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDStatus. diff --git a/api/v1beta1/argocd_types.go b/api/v1beta1/argocd_types.go index 28419448c..3f9d5bba1 100644 --- a/api/v1beta1/argocd_types.go +++ b/api/v1beta1/argocd_types.go @@ -935,6 +935,15 @@ type ArgoCDSpec struct { AggregatedClusterRoles bool `json:"aggregatedClusterRoles,omitempty"` } +const ( + ArgoCDConditionType = "Reconciled" +) + +const ( + ArgoCDConditionReasonSuccess = "Success" + ArgoCDConditionReasonErrorOccurred = "ErrorOccurred" +) + // ArgoCDStatus defines the observed state of ArgoCD // +k8s:openapi-gen=true type ArgoCDStatus struct { @@ -1018,6 +1027,9 @@ type ArgoCDStatus struct { // Host is the hostname of the Ingress. Host string `json:"host,omitempty"` + + // Conditions is an array of the ArgoCD's status conditions + Conditions []metav1.Condition `json:"conditions,omitempty"` } // Banner defines an additional banner message to be displayed in Argo CD UI diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 36f2e42ad..567bca3c7 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -35,7 +35,7 @@ func (in *ArgoCD) DeepCopyInto(out *ArgoCD) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCD. @@ -1026,6 +1026,13 @@ func (in *ArgoCDSpec) DeepCopy() *ArgoCDSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoCDStatus) DeepCopyInto(out *ArgoCDStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDStatus. diff --git a/config/crd/bases/argoproj.io_argocds.yaml b/config/crd/bases/argoproj.io_argocds.yaml index 1feb92b47..fc3dac432 100644 --- a/config/crd/bases/argoproj.io_argocds.yaml +++ b/config/crd/bases/argoproj.io_argocds.yaml @@ -7152,6 +7152,76 @@ spec: Failed: At least one of the Argo CD applicationSet controller component Pods had a failure. Unknown: The state of the Argo CD applicationSet controller component could not be obtained. type: string + conditions: + description: Conditions is an array of the ArgoCD's status conditions + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array host: description: Host is the hostname of the Ingress. type: string @@ -24654,6 +24724,76 @@ spec: Failed: At least one of the Argo CD applicationSet controller component Pods had a failure. Unknown: The state of the Argo CD applicationSet controller component could not be obtained. type: string + conditions: + description: Conditions is an array of the ArgoCD's status conditions + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array host: description: Host is the hostname of the Ingress. type: string diff --git a/controllers/argocd/argocd_controller.go b/controllers/argocd/argocd_controller.go index 32945b57c..974c8e477 100644 --- a/controllers/argocd/argocd_controller.go +++ b/controllers/argocd/argocd_controller.go @@ -117,11 +117,26 @@ func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) ( labelSelector, err := labels.Parse(r.LabelSelector) if err != nil { reqLogger.Info(fmt.Sprintf("error parsing the labelSelector '%s'.", labelSelector)) + + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, err } + // Match the value of labelSelector from ReconcileArgoCD to labels from the argocd instance if !labelSelector.Matches(labels.Set(argocd.Labels)) { reqLogger.Info(fmt.Sprintf("the ArgoCD instance '%s' does not match the label selector '%s' and skipping for reconciliation", request.NamespacedName, r.LabelSelector)) + + if error := updateStatusConditionOfArgoCD(ctx, + createCondition(fmt.Sprintf("the ArgoCD instance '%s' does not match the label selector '%s' and skipping for reconciliation", request.NamespacedName, r.LabelSelector)), + argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, fmt.Errorf("error: failed to reconcile ArgoCD instance: '%s'", request.NamespacedName) } @@ -161,24 +176,49 @@ func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) ( if argocd.IsDeletionFinalizerPresent() { if err := r.deleteClusterResources(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(fmt.Sprintf("failed to delete ClusterResources: %v", err)), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, fmt.Errorf("failed to delete ClusterResources: %w", err) } if isRemoveManagedByLabelOnArgoCDDeletion() { if err := r.removeManagedByLabelFromNamespaces(argocd.Namespace); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(fmt.Sprintf("failed to remove label from namespace[%v], error: %v", argocd.Namespace, err)), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, fmt.Errorf("failed to remove label from namespace[%v], error: %w", argocd.Namespace, err) } } if err := r.removeUnmanagedSourceNamespaceResources(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(fmt.Sprintf("failed to remove resources from sourceNamespaces, error: %v", err)), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, fmt.Errorf("failed to remove resources from sourceNamespaces, error: %w", err) } if err := r.removeUnmanagedApplicationSetSourceNamespaceResources(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(fmt.Sprintf("failed to remove resources from applicationSetSourceNamespaces, error: %v", err)), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, fmt.Errorf("failed to remove resources from applicationSetSourceNamespaces, error: %w", err) } if err := r.removeDeletionFinalizer(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, err } @@ -186,38 +226,73 @@ func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) ( // is created in the same namespace in the future, that instance is appropriately tracked delete(DeprecationEventEmissionTracker, argocd.Namespace) } + + if error := updateStatusConditionOfArgoCD(ctx, createCondition(""), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } return reconcile.Result{}, nil } if !argocd.IsDeletionFinalizerPresent() { if err := r.addDeletionFinalizer(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, err } } // get the latest version of argocd instance before reconciling if err = r.Client.Get(ctx, request.NamespacedName, argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } return reconcile.Result{}, err } if err = r.setManagedNamespaces(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } return reconcile.Result{}, err } if err = r.setManagedSourceNamespaces(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } return reconcile.Result{}, err } if err = r.setManagedApplicationSetSourceNamespaces(argocd); err != nil { + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } + return reconcile.Result{}, err } if err := r.reconcileResources(argocd); err != nil { // Error reconciling ArgoCD sub-resources - requeue the request. + if error := updateStatusConditionOfArgoCD(ctx, createCondition(err.Error()), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } return reconcile.Result{}, err } // Return and don't requeue + if error := updateStatusConditionOfArgoCD(ctx, createCondition(""), argocd, r.Client, log); error != nil { + log.Error(error, "unable to update status of ArgoCD") + return reconcile.Result{}, error + } return reconcile.Result{}, nil } diff --git a/controllers/argocd/argocd_controller_test.go b/controllers/argocd/argocd_controller_test.go index 0b105c65f..4cee0ae4d 100644 --- a/controllers/argocd/argocd_controller_test.go +++ b/controllers/argocd/argocd_controller_test.go @@ -384,3 +384,42 @@ func clusterResources(argocd *argoproj.ArgoCD) []client.Object { newClusterRoleBindingWithname(common.ArgoCDServerComponent, argocd), } } + +func TestReconcileArgoCD_Status_Condition(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + + a := makeTestArgoCD(func(ac *argoproj.ArgoCD) { + ac.Name = "argo-test-2" + ac.Labels = map[string]string{"testfoo": "testbar"} + }) + + resObjs := []client.Object{a} + subresObjs := []client.Object{a} + runtimeObjs := []runtime.Object{} + sch := makeTestReconcilerScheme(argoproj.AddToScheme) + cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) + rt := makeTestReconciler(cl, sch) + rt.LabelSelector = "foo=bar" + assert.NoError(t, createNamespace(rt, a.Namespace, "")) + + // Instance is not reconciled as the label does not match, error expected + reqTest := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: a.Name, + Namespace: a.Namespace, + }, + } + resTest, err := rt.Reconcile(context.TODO(), reqTest) + assert.Error(t, err) + if resTest.Requeue { + t.Fatal("reconcile requeued request") + } + + // Verify condition is updated + assert.NoError(t, rt.Client.Get(context.TODO(), types.NamespacedName{Name: a.Name, Namespace: a.Namespace}, a)) + + assert.Equal(t, a.Status.Conditions[0].Type, argoproj.ArgoCDConditionType) + assert.Equal(t, a.Status.Conditions[0].Reason, argoproj.ArgoCDConditionReasonErrorOccurred) + assert.Equal(t, a.Status.Conditions[0].Message, "the ArgoCD instance 'argocd/argo-test-2' does not match the label selector 'foo=bar' and skipping for reconciliation") + assert.Equal(t, a.Status.Conditions[0].Status, metav1.ConditionFalse) +} diff --git a/controllers/argocd/util.go b/controllers/argocd/util.go index aa481dc43..415f3e559 100644 --- a/controllers/argocd/util.go +++ b/controllers/argocd/util.go @@ -52,6 +52,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -1664,3 +1665,79 @@ func getApplicationSetHTTPServerHost(cr *argoproj.ArgoCD) (string, error) { } return host, nil } + +// updateStatusConditionOfArgoCD calls Set Condition of ArgoCD status +func updateStatusConditionOfArgoCD(ctx context.Context, condition metav1.Condition, cr *argoproj.ArgoCD, k8sClient client.Client, log logr.Logger) error { + changed, newConditions := insertOrUpdateConditionsInSlice(condition, cr.Status.Conditions) + + if changed { + // get the latest version of argocd instance before updating + if err := k8sClient.Get(ctx, types.NamespacedName{Name: cr.Name, Namespace: cr.Namespace}, cr); err != nil { + return err + } + + cr.Status.Conditions = newConditions + + if err := k8sClient.Status().Update(ctx, cr); err != nil { + log.Error(err, "unable to update RolloutManager status condition") + return err + } + } + return nil +} + +// insertOrUpdateConditionsInSlice is a generic function for inserting/updating metav1.Condition into a slice of []metav1.Condition +func insertOrUpdateConditionsInSlice(newCondition metav1.Condition, existingConditions []metav1.Condition) (bool, []metav1.Condition) { + + // Check if condition with same type is already set, if Yes then check if content is same, + // If content is not same update LastTransitionTime + index := -1 + for i, Condition := range existingConditions { + if Condition.Type == newCondition.Type { + index = i + break + } + } + + now := metav1.Now() + + changed := false + + if index == -1 { + newCondition.LastTransitionTime = now + existingConditions = append(existingConditions, newCondition) + changed = true + + } else if existingConditions[index].Message != newCondition.Message || + existingConditions[index].Reason != newCondition.Reason || + existingConditions[index].Status != newCondition.Status { + + newCondition.LastTransitionTime = now + existingConditions[index] = newCondition + changed = true + } + + return changed, existingConditions + +} + +// createCondition returns Condition based on input provided. +// 1. Returns Success condition if no error message is provided, all fields are default. +// 2. If Message is provided, it returns Failed condition having all default fields except Message. +func createCondition(message string) metav1.Condition { + if message == "" { + return metav1.Condition{ + Type: argoproj.ArgoCDConditionType, + Reason: argoproj.ArgoCDConditionReasonSuccess, + Message: "", + Status: metav1.ConditionTrue, + } + } + + return metav1.Condition{ + Type: argoproj.ArgoCDConditionType, + Reason: argoproj.ArgoCDConditionReasonErrorOccurred, + Message: message, + Status: metav1.ConditionFalse, + } +} diff --git a/controllers/argocd/util_test.go b/controllers/argocd/util_test.go index 68bcba92b..b1b2709f0 100644 --- a/controllers/argocd/util_test.go +++ b/controllers/argocd/util_test.go @@ -1094,3 +1094,75 @@ func TestReconcileArgoCD_reconcileDexOAuthClientSecret(t *testing.T) { } assert.True(t, tokenExists, "Dex is enabled but unable to create oauth client secret") } + +func TestUpdateStatusConditionOfArgoCD_Success(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + ctx := context.Background() + a := makeTestArgoCD(deletedAt(time.Now())) + resObjs := []client.Object{a} + subresObjs := []client.Object{a} + runtimeObjs := []runtime.Object{} + sch := makeTestReconcilerScheme(argoproj.AddToScheme) + cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) + r := makeTestReconciler(cl, sch) + + argocd := argoproj.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-rm-1", + Namespace: "test-ns-1", + }, + } + + assert.NoError(t, createNamespace(r, argocd.Namespace, "")) + assert.NoError(t, r.Client.Create(ctx, &argocd)) + assert.NoError(t, updateStatusConditionOfArgoCD(ctx, createCondition(""), &argocd, r.Client, log)) + + assert.Equal(t, argocd.Status.Conditions[0].Type, argoproj.ArgoCDConditionType) + assert.Equal(t, argocd.Status.Conditions[0].Reason, argoproj.ArgoCDConditionReasonSuccess) + assert.Equal(t, argocd.Status.Conditions[0].Message, "") + assert.Equal(t, argocd.Status.Conditions[0].Status, metav1.ConditionTrue) +} + +func TestUpdateStatusConditionOfArgoCD_Fail(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + ctx := context.Background() + a := makeTestArgoCD(deletedAt(time.Now())) + resObjs := []client.Object{a} + subresObjs := []client.Object{a} + runtimeObjs := []runtime.Object{} + sch := makeTestReconcilerScheme(argoproj.AddToScheme) + cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) + r := makeTestReconciler(cl, sch) + + argocd := argoproj.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-rm-1", + Namespace: "test-ns-1", + }, + } + + assert.NoError(t, createNamespace(r, argocd.Namespace, "")) + assert.NoError(t, r.Client.Create(ctx, &argocd)) + assert.NoError(t, updateStatusConditionOfArgoCD(ctx, createCondition("some error"), &argocd, r.Client, log)) + + assert.Equal(t, argocd.Status.Conditions[0].Type, argoproj.ArgoCDConditionType) + assert.Equal(t, argocd.Status.Conditions[0].Reason, argoproj.ArgoCDConditionReasonErrorOccurred) + assert.Equal(t, argocd.Status.Conditions[0].Message, "some error") + assert.Equal(t, argocd.Status.Conditions[0].Status, metav1.ConditionFalse) + + // Update error condition + assert.NoError(t, updateStatusConditionOfArgoCD(ctx, createCondition("some other error"), &argocd, r.Client, log)) + + assert.Equal(t, argocd.Status.Conditions[0].Type, argoproj.ArgoCDConditionType) + assert.Equal(t, argocd.Status.Conditions[0].Reason, argoproj.ArgoCDConditionReasonErrorOccurred) + assert.Equal(t, argocd.Status.Conditions[0].Message, "some other error") + assert.Equal(t, argocd.Status.Conditions[0].Status, metav1.ConditionFalse) + + // Update success condition + assert.NoError(t, updateStatusConditionOfArgoCD(ctx, createCondition(""), &argocd, r.Client, log)) + + assert.Equal(t, argocd.Status.Conditions[0].Type, argoproj.ArgoCDConditionType) + assert.Equal(t, argocd.Status.Conditions[0].Reason, argoproj.ArgoCDConditionReasonSuccess) + assert.Equal(t, argocd.Status.Conditions[0].Message, "") + assert.Equal(t, argocd.Status.Conditions[0].Status, metav1.ConditionTrue) +} diff --git a/tests/k8s/1-001_validate_basic/01-assert.yaml b/tests/k8s/1-001_validate_basic/01-assert.yaml index c375020fc..bc7082a83 100644 --- a/tests/k8s/1-001_validate_basic/01-assert.yaml +++ b/tests/k8s/1-001_validate_basic/01-assert.yaml @@ -10,6 +10,11 @@ metadata: name: example-argocd status: phase: Available + conditions: + - message: "" + reason: Success + status: "True" + type: Reconciled --- apiVersion: apps/v1 kind: StatefulSet