Repository: argoproj-labs/argocd-operator
Pull request #844 (2023-01-19 13:39:31)

feat: allow users to add command arguments to repo server using Argo CD CR
Signed-off-by: iam-veeramalla <abhishek.veeramalla@gmail.com>

**What type of PR is this?**
> /kind enhancement

**What does this PR do / why we need it**:
Allows users to add command arguments to repo server using Argo CD CR.

For example, Assume Argo CD upstream has introduced a new command argument that can be added to `repo-server` deployment to achieve a specific task. The Argo CD Operator users should wait for the maintainers to implement this in the operator else the command arguments added by the user will be reconciled back and removed.

While the maintainers of Argo CD operator always aims to get such changes from Argo CD to the operator as soon as possible, there is a fair chance of release getting delayed or the maintainers forget about a specific command argument. 

More over, such changes are always very tedious as it involves in changing a lot of files
 - argocd_types.go
 - run `make generate manifests`
 - Add logic to the `deployment.go`
 - Add unit tests
 - Add E2E tests

This PR aims to solve all the above challenges by introducing a new field in the ArgoCD CR for repo server -> `extraRepoCommandArgs`.

Provided required examples in the docs as well as E2E tests. PTAL

**Have you updated the necessary documentation?**
* [x] Documentation update is required by this PR.
* [x] Documentation has been updated.

**Which issue(s) this PR fixes**:
Fixes #?

**How to test changes / Special notes to the reviewer**:
1. Run the operator locally using `make run install`
2. Create a Argo CD Instance with `extraRepoCommandArgs` as shown below.
```
apiVersion: argoproj.io/v1alpha1
kind: ArgoCD
metadata:
  name: example-argocd
spec:
  repo:
    extraRepoCommandArgs:
      - --reposerver.max.combined.directory.manifests.size
      - 10M
```
3. Wait for the operator to reconcile the CR and deploy the worloads.
4. Make sure the above mentioned command arguments are placed in the `command` section of Argo CD Repo Server Deployment.

(or)

You can also run the E2E test
`kubectl kuttl test ./tests/k8s --config ./tests/kuttl-tests.yaml --test 1-028_validate_extra_repo_commands_args`



Patch:
diff --git a/api/v1alpha1/argocd_types.go b/api/v1alpha1/argocd_types.go
index 71490097..6a35a87b 100644
--- a/api/v1alpha1/argocd_types.go
+++ b/api/v1alpha1/argocd_types.go
@@ -392,6 +392,11 @@ type ArgoCDRedisSpec struct {
 // ArgoCDRepoSpec defines the desired state for the Argo CD repo server component.
 type ArgoCDRepoSpec struct {
 
+	// Extra Command arguments allows users to pass command line arguments to repo server workload. They get added to default command line arguments provided
+	// by the operator.
+	// Please note that the command line arguments provided as part of ExtraRepoCommandArgs will not overwrite the default command line arguments.
+	ExtraRepoCommandArgs []string `json:"extraRepoCommandArgs,omitempty"`
+
 	// LogLevel describes the log level that should be used by the Repo Server. Defaults to ArgoCDDefaultLogLevel if not set.  Valid options are debug, info, error, and warn.
 	LogLevel string `json:"logLevel,omitempty"`
 
@@ -614,6 +619,15 @@ type KustomizeVersionSpec struct {
 	Path string `json:"path,omitempty"`
 }
 
+// ArgoCDMonitoringSpec is used to configure workload status monitoring for a given Argo CD instance.
+// It triggers creation of serviceMonitor and PrometheusRules that alert users when a given workload
+// status meets a certain criteria. For e.g, it can fire an alert if the application controller is
+// pending for x mins consecutively.
+type ArgoCDMonitoringSpec struct {
+	// Enabled defines whether workload status monitoring is enabled for this instance or not
+	Enabled bool `json:"enabled"`
+}
+
 //ArgoCDNodePlacementSpec is used to specify NodeSelector and Tolerations for Argo CD workloads
 type ArgoCDNodePlacementSpec struct {
 	// NodeSelector is a field of PodSpec, it is a map of key value pairs used for node selection
@@ -701,6 +715,9 @@ type ArgoCDSpec struct {
 	//+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="OIDC Config'",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text","urn:alm:descriptor:com.tectonic.ui:advanced"}
 	OIDCConfig string `json:"oidcConfig,omitempty"`
 
+	// Monitoring defines whether workload status monitoring configuration for this instance.
+	Monitoring ArgoCDMonitoringSpec `json:"monitoring,omitempty"`
+
 	// NodePlacement defines NodeSelectors and Taints for Argo CD workloads
 	NodePlacement *ArgoCDNodePlacementSpec `json:"nodePlacement,omitempty"`
 
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index f98cbe95..81335716 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -489,6 +489,21 @@ func (in *ArgoCDList) DeepCopyObject() runtime.Object {
 	return nil
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ArgoCDMonitoringSpec) DeepCopyInto(out *ArgoCDMonitoringSpec) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDMonitoringSpec.
+func (in *ArgoCDMonitoringSpec) DeepCopy() *ArgoCDMonitoringSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ArgoCDMonitoringSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ArgoCDNodePlacementSpec) DeepCopyInto(out *ArgoCDNodePlacementSpec) {
 	*out = *in
@@ -630,6 +645,11 @@ func (in *ArgoCDRedisSpec) DeepCopy() *ArgoCDRedisSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ArgoCDRepoSpec) DeepCopyInto(out *ArgoCDRepoSpec) {
 	*out = *in
+	if in.ExtraRepoCommandArgs != nil {
+		in, out := &in.ExtraRepoCommandArgs, &out.ExtraRepoCommandArgs
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	if in.Replicas != nil {
 		in, out := &in.Replicas, &out.Replicas
 		*out = new(int32)
@@ -893,6 +913,7 @@ func (in *ArgoCDSpec) DeepCopyInto(out *ArgoCDSpec) {
 		*out = make([]KustomizeVersionSpec, len(*in))
 		copy(*out, *in)
 	}
+	out.Monitoring = in.Monitoring
 	if in.NodePlacement != nil {
 		in, out := &in.NodePlacement, &out.NodePlacement
 		*out = new(ArgoCDNodePlacementSpec)
diff --git a/bundle/manifests/argoproj.io_argocds.yaml b/bundle/manifests/argoproj.io_argocds.yaml
index b3ffacae..c45f9dd0 100644
--- a/bundle/manifests/argoproj.io_argocds.yaml
+++ b/bundle/manifests/argoproj.io_argocds.yaml
@@ -782,6 +782,17 @@ spec:
                       type: string
                   type: object
                 type: array
+              monitoring:
+                description: Monitoring defines whether workload status monitoring
+                  configuration for this instance.
+                properties:
+                  enabled:
+                    description: Enabled defines whether workload status monitoring
+                      is enabled for this instance or not
+                    type: boolean
+                required:
+                - enabled
+                type: object
               nodePlacement:
                 description: NodePlacement defines NodeSelectors and Taints for Argo
                   CD workloads
@@ -1346,6 +1357,15 @@ spec:
                     description: ExecTimeout specifies the timeout in seconds for
                       tool execution
                     type: integer
+                  extraRepoCommandArgs:
+                    description: Extra Command arguments allows users to pass command
+                      line arguments to repo server workload. They get added to default
+                      command line arguments provided by the operator. Please note
+                      that the command line arguments provided as part of ExtraRepoCommandArgs
+                      will not overwrite the default command line arguments.
+                    items:
+                      type: string
+                    type: array
                   image:
                     description: Image is the ArgoCD Repo Server container image.
                     type: string
diff --git a/config/crd/bases/argoproj.io_argocds.yaml b/config/crd/bases/argoproj.io_argocds.yaml
index 3cce9a95..077131db 100644
--- a/config/crd/bases/argoproj.io_argocds.yaml
+++ b/config/crd/bases/argoproj.io_argocds.yaml
@@ -784,6 +784,17 @@ spec:
                       type: string
                   type: object
                 type: array
+              monitoring:
+                description: Monitoring defines whether workload status monitoring
+                  configuration for this instance.
+                properties:
+                  enabled:
+                    description: Enabled defines whether workload status monitoring
+                      is enabled for this instance or not
+                    type: boolean
+                required:
+                - enabled
+                type: object
               nodePlacement:
                 description: NodePlacement defines NodeSelectors and Taints for Argo
                   CD workloads
@@ -1348,6 +1359,15 @@ spec:
                     description: ExecTimeout specifies the timeout in seconds for
                       tool execution
                     type: integer
+                  extraRepoCommandArgs:
+                    description: Extra Command arguments allows users to pass command
+                      line arguments to repo server workload. They get added to default
+                      command line arguments provided by the operator. Please note
+                      that the command line arguments provided as part of ExtraRepoCommandArgs
+                      will not overwrite the default command line arguments.
+                    items:
+                      type: string
+                    type: array
                   image:
                     description: Image is the ArgoCD Repo Server container image.
                     type: string
diff --git a/controllers/argocd/deployment.go b/controllers/argocd/deployment.go
index ac2fb404..2684ac03 100644
--- a/controllers/argocd/deployment.go
+++ b/controllers/argocd/deployment.go
@@ -250,6 +250,15 @@ func getArgoRepoCommand(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) []string {
 	cmd = append(cmd, "--logformat")
 	cmd = append(cmd, getLogFormat(cr.Spec.Repo.LogFormat))
 
+	// *** NOTE ***
+	// Do Not add any new default command line arguments below this.
+	extraArgs := cr.Spec.Repo.ExtraRepoCommandArgs
+	err := isMergable(extraArgs, cmd)
+	if err != nil {
+		return cmd
+	}
+
+	cmd = append(cmd, extraArgs...)
 	return cmd
 }
 
@@ -316,14 +325,14 @@ func getArgoServerCommand(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) []string
 	return cmd
 }
 
-// isMergable returns error if any of the extraCommandArgs already exists in the Argo CD server cmd.
+// isMergable returns error if any of the extraArgs is already part of the default command Arguments.
 func isMergable(extraArgs []string, cmd []string) error {
 	if len(extraArgs) > 0 {
 		for _, arg := range extraArgs {
 			if len(arg) > 2 && arg[:2] == "--" {
 				if ok := contains(cmd, arg); ok {
 					err := errors.New("duplicate argument error")
-					log.Error(err, fmt.Sprintf("Arg %s is already part of the Argo CD server command", arg))
+					log.Error(err, fmt.Sprintf("Arg %s is already part of the default command arguments", arg))
 					return err
 				}
 			}
diff --git a/controllers/argocd/prometheus.go b/controllers/argocd/prometheus.go
index 88578a14..23675785 100644
--- a/controllers/argocd/prometheus.go
+++ b/controllers/argocd/prometheus.go
@@ -20,6 +20,7 @@ import (
 
 	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 
 	argoprojv1a1 "github.com/argoproj-labs/argocd-operator/api/v1alpha1"
@@ -247,3 +248,150 @@ func (r *ReconcileArgoCD) reconcileServerMetricsServiceMonitor(cr *argoprojv1a1.
 	}
 	return r.Client.Create(context.TODO(), sm)
 }
+
+// reconcilePrometheusRule reconciles the PrometheusRule that triggers alerts based on workload statuses
+func (r *ReconcileArgoCD) reconcilePrometheusRule(cr *argoprojv1a1.ArgoCD) error {
+
+	promRule := newPrometheusRule(cr.Namespace, "argocd-component-status-alert")
+
+	if argoutil.IsObjectFound(r.Client, cr.Namespace, promRule.Name, promRule) {
+
+		if !cr.Spec.Monitoring.Enabled {
+			// PrometheusRule exists but enabled flag has been set to false, delete the PrometheusRule
+			log.Info("instance monitoring disabled, deleting component status tracking prometheusRule")
+			return r.Client.Delete(context.TODO(), promRule)
+		}
+		return nil // PrometheusRule found, do nothing
+	}
+
+	if !cr.Spec.Monitoring.Enabled {
+		return nil // Monitoring not enabled, do nothing.
+	}
+
+	ruleGroups := []monitoringv1.RuleGroup{
+		{
+			Name: "ArgoCDComponentStatus",
+			Rules: []monitoringv1.Rule{
+				{
+					Alert: "ApplicationControllerNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("application controller deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_statefulset_status_replicas{statefulset=\"%s\", namespace=\"%s\"} != kube_statefulset_status_replicas_ready{statefulset=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-application-controller"), cr.Namespace, fmt.Sprintf(cr.Name+"-application-controller"), cr.Namespace),
+					},
+					For: "1m",
+					Labels: map[string]string{
+						"severity": "critical",
+					},
+				},
+				{
+					Alert: "ServerNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("server deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-server"), cr.Namespace, fmt.Sprintf(cr.Name+"-server"), cr.Namespace),
+					},
+					For: "1m",
+					Labels: map[string]string{
+						"severity": "critical",
+					},
+				},
+				{
+					Alert: "RepoServerNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("repo server deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-repo-server"), cr.Namespace, fmt.Sprintf(cr.Name+"-repo-server"), cr.Namespace),
+					},
+					For: "1m",
+					Labels: map[string]string{
+						"severity": "critical",
+					},
+				},
+				{
+					Alert: "ApplicationSetControllerNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("applicationSet controller deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-applicationset-controller"), cr.Namespace, fmt.Sprintf(cr.Name+"-applicationset-controller"), cr.Namespace),
+					},
+					For: "5m",
+					Labels: map[string]string{
+						"severity": "warning",
+					},
+				},
+				{
+					Alert: "DexNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("dex deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-dex-server"), cr.Namespace, fmt.Sprintf(cr.Name+"-dex-server"), cr.Namespace),
+					},
+					For: "5m",
+					Labels: map[string]string{
+						"severity": "warning",
+					},
+				},
+				{
+					Alert: "NotificationsControllerNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("notifications controller deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-notifications-controller"), cr.Namespace, fmt.Sprintf(cr.Name+"-notifications-controller"), cr.Namespace),
+					},
+					For: "5m",
+					Labels: map[string]string{
+						"severity": "warning",
+					},
+				},
+				{
+					Alert: "RedisNotReady",
+					Annotations: map[string]string{
+						"message": fmt.Sprintf("redis deployment for Argo CD instance in namespace %s is not running", cr.Namespace),
+					},
+					Expr: intstr.IntOrString{
+						Type:   intstr.String,
+						StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(cr.Name+"-redis"), cr.Namespace, fmt.Sprintf(cr.Name+"-redis"), cr.Namespace),
+					},
+					For: "5m",
+					Labels: map[string]string{
+						"severity": "warning",
+					},
+				},
+			},
+		},
+	}
+	promRule.Spec.Groups = ruleGroups
+
+	if err := controllerutil.SetControllerReference(cr, promRule, r.Scheme); err != nil {
+		return err
+	}
+
+	log.Info("instance monitoring enabled, creating component status tracking prometheusRule")
+	return r.Client.Create(context.TODO(), promRule) // Create PrometheusRule
+}
+
+// newPrometheusRule returns an empty PrometheusRule
+func newPrometheusRule(namespace, alertRuleName string) *monitoringv1.PrometheusRule {
+
+	promRule := &monitoringv1.PrometheusRule{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      alertRuleName,
+			Namespace: namespace,
+		},
+		Spec: monitoringv1.PrometheusRuleSpec{},
+	}
+	return promRule
+}
diff --git a/controllers/argocd/util.go b/controllers/argocd/util.go
index fc93596c..a761613f 100644
--- a/controllers/argocd/util.go
+++ b/controllers/argocd/util.go
@@ -797,6 +797,11 @@ func (r *ReconcileArgoCD) reconcileResources(cr *argoprojv1a1.ArgoCD) error {
 			return err
 		}
 
+		// Reconciles prometheusRule created to alert based on argo-cd workload status
+		if err := r.reconcilePrometheusRule(cr); err != nil {
+			return err
+		}
+
 		if err := r.reconcileMetricsServiceMonitor(cr); err != nil {
 			return err
 		}
diff --git a/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_argocds.yaml b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_argocds.yaml
index b3ffacae..c45f9dd0 100644
--- a/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_argocds.yaml
+++ b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_argocds.yaml
@@ -782,6 +782,17 @@ spec:
                       type: string
                   type: object
                 type: array
+              monitoring:
+                description: Monitoring defines whether workload status monitoring
+                  configuration for this instance.
+                properties:
+                  enabled:
+                    description: Enabled defines whether workload status monitoring
+                      is enabled for this instance or not
+                    type: boolean
+                required:
+                - enabled
+                type: object
               nodePlacement:
                 description: NodePlacement defines NodeSelectors and Taints for Argo
                   CD workloads
@@ -1346,6 +1357,15 @@ spec:
                     description: ExecTimeout specifies the timeout in seconds for
                       tool execution
                     type: integer
+                  extraRepoCommandArgs:
+                    description: Extra Command arguments allows users to pass command
+                      line arguments to repo server workload. They get added to default
+                      command line arguments provided by the operator. Please note
+                      that the command line arguments provided as part of ExtraRepoCommandArgs
+                      will not overwrite the default command line arguments.
+                    items:
+                      type: string
+                    type: array
                   image:
                     description: Image is the ArgoCD Repo Server container image.
                     type: string
diff --git a/docs/reference/argocd.md b/docs/reference/argocd.md
index c5dfec99..1633fad3 100644
--- a/docs/reference/argocd.md
+++ b/docs/reference/argocd.md
@@ -921,6 +921,7 @@ The following properties are available for configuring the Repo server component
 
 Name | Default | Description
 --- | --- | ---
+[ExtraRepoCommandArgs](#pass-command-arguments-to-repo-server) | [Empty] | Extra Command arguments allows users to pass command line arguments to repo server workload. They get added to default command line arguments provided by the operator.
 Resources | [Empty] | The container compute resources.
 MountSAToken | false | Whether the ServiceAccount token should be mounted to the repo-server pod.
 ServiceAccount | "" | The name of the ServiceAccount to use with the repo-server pod.
@@ -932,9 +933,21 @@ LogLevel | info | The log level to be used by the ArgoCD Repo Server. Valid opti
 LogFormat | text | The log format to be used by the ArgoCD Repo Server. Valid options are text or json.
 ExecTimeout | 180 | Execution timeout in seconds for rendering tools (e.g. Helm, Kustomize)
 Env | [Empty] | Environment to set for the repository server workloads
-Replicas | [Empty] | The number of replicas for the ArgoCD Repo Server. Must be greater than or equal to 0. 
+Replicas | [Empty] | The number of replicas for the ArgoCD Repo Server. Must be greater than or equal to 0.
 
-### Repo Example
+### Pass Command Arguments To Repo Server
+
+Allows a user to pass additional arguments to Argo CD Repo Server command.
+
+Name | Default | Description
+--- | --- | ---
+ExtraCommandArgs | [Empty] | Extra Command arguments allows users to pass command line arguments to repo server workload. They get added to default command line arguments
+provided by the operator.
+
+!!! note
+    The command line arguments provided as part of ExtraRepoCommandArgs will not overwrite the default command line arguments created by the operator.
+
+### Repo Server Example
 
 The following example shows all properties set to the default values.
 
@@ -955,6 +968,22 @@ spec:
     replicas: 1
 ```
 
+### Repo Server Command Arguments Example
+
+``` yaml
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+  labels:
+    example: server
+spec:
+  repo:
+    extraRepoCommandArgs:
+      - --reposerver.max.combined.directory.manifests.size
+      - 10M
+```
+
 ## Resource Customizations
 
 There are two ways to customize resource behavior- the first way, only available with release v0.5.0+, is with subkeys (`resourceHealthChecks`, `resourceIgnoreDifferences`, and `resourceActions`), the second is without subkeys (`resourceCustomizations`). `resourceCustomizations` maps directly to the `resource.customizations` field in the `argocd-cm` ConfigMap, while each of the subkeys maps directly to their own field in the `argocd-cm`. `resourceHealthChecks` will map to `resource.customizations.health`, `resourceIgnoreDifferences` to `resource.customizations.ignoreDifferences`, and `resourceActions` to `resource.customizations.actions`.
diff --git a/docs/usage/monitoring.md b/docs/usage/monitoring.md
new file mode 100644
index 00000000..f0342883
--- /dev/null
+++ b/docs/usage/monitoring.md
@@ -0,0 +1,79 @@
+# Instance workload monitoring 
+
+Instance workload monitoring allows you to be better informed of the state of your Argo CD instance by enabling alerts for the instance. Alerts are triggered when the instance's component workload (application-controller, repo-server, server etc.) pods are unable to come up for whatever reason and there is a drift between the number of ready replicas and the number of desired replicas for a certain period of time.  
+
+**Note:** Pre-requisites for this feature:
+- Your cluster is configured with Prometheus, and the Argo CD instance is in a namespace that can be monitored via Prometheus
+- Your cluster has the `kube-state-metrics` service running 
+
+**Note:** This feature is only concerned with availability of Argo CD workloads, and does not focus on monitoring for performance. 
+
+
+Instance workload monitoring can be enabled by setting `.spec.monitoring.enabled` to `true` on a given Argo CD instance.
+For example:
+
+```
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+  labels:
+    example: example-argocd
+spec:
+  ...
+  monitoring:
+    enabled: true
+  ...
+```
+
+Instance workload monitoring is set to `false` by default.
+
+Enabling this setting allows the operator to create a `PrometheusRule` containing pre-configured alert rules for all the workloads 9statefulsets/deployments) managed by the instance. Here is a sample alert rule included in the PrometheusRule created by the operator:
+
+```
+apiVersion: monitoring.coreos.com/v1
+kind: PrometheusRule
+metadata:
+  name: argocd-component-status-alert
+  namespace: $NAMESPACE
+spec:
+  groups:
+    - name: ArgoCDComponentStatus
+      rules:
+        ...
+        - alert: ApplicationSetControllerNotReady
+          annotations:
+            message: >-
+              applicationSet controller deployment for Argo CD instance in
+              namespace "default" is not running
+          expr: >-
+            kube_deployment_status_replicas{deployment="argocd-applicationset-controller",
+            namespace="default"} !=
+            kube_deployment_status_replicas_ready{deployment="argocd-applicationset-controller",
+            namespace="default"} 
+          for: 10m
+          labels:
+            severity: warning
+        ...
+```
+
+Users are free to modify/delete alert rules as wish, changes made to the rules will not be overwritten by the operator. 
+
+Instance workload monitoring can be disabled by setting `.spec.monitoring.enabled` to `false` on a given Argo CD instance.
+For example:
+
+```
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+  labels:
+    example: example-argocd
+spec:
+  ...
+  monitoring:
+    enabled: false
+  ...
+```
+
+Disabling workload monitoring will delete the created PrometheusRule. 
\ No newline at end of file


Test patch:
diff --git a/controllers/argocd/deployment_test.go b/controllers/argocd/deployment_test.go
index aac4e9f0..7cb5b378 100644
--- a/controllers/argocd/deployment_test.go
+++ b/controllers/argocd/deployment_test.go
@@ -1651,3 +1651,94 @@ func TestReconcileArgoCD_reconcile_RepoServerChanges(t *testing.T) {
 		})
 	}
 }
+
+func TestArgoCDRepoServerDeploymentCommand(t *testing.T) {
+	a := makeTestArgoCD()
+	r := makeTestReconciler(t, a)
+
+	testRedisServerAddress := getRedisServerAddress(a)
+
+	baseCommand := []string{
+		"uid_entrypoint.sh",
+		"argocd-repo-server",
+		"--redis",
+		testRedisServerAddress,
+		"--loglevel",
+		"info",
+		"--logformat",
+		"text",
+	}
+
+	// When a single command argument is passed
+	a.Spec.Repo.ExtraRepoCommandArgs = []string{
+		"--reposerver.max.combined.directory.manifests.size",
+		"10M",
+	}
+
+	deployment := &appsv1.Deployment{}
+	assert.NoError(t, r.reconcileRepoDeployment(a, false))
+
+	assert.NoError(t, r.Client.Get(
+		context.TODO(),
+		types.NamespacedName{
+			Name:      "argocd-repo-server",
+			Namespace: a.Namespace,
+		},
+		deployment))
+
+	cmd := append(baseCommand,
+		"--reposerver.max.combined.directory.manifests.size", "10M")
+	assert.Equal(t, cmd, deployment.Spec.Template.Spec.Containers[0].Command)
+
+	// When multiple command arguments are passed
+	a.Spec.Repo.ExtraRepoCommandArgs = []string{
+		"--reposerver.max.combined.directory.manifests.size",
+		"10M",
+		"--foo",
+		"bar",
+		"test",
+	}
+
+	assert.NoError(t, r.reconcileRepoDeployment(a, false))
+	assert.NoError(t, r.Client.Get(
+		context.TODO(),
+		types.NamespacedName{
+			Name:      "argocd-repo-server",
+			Namespace: a.Namespace,
+		},
+		deployment))
+
+	cmd = append(cmd, "--foo", "bar", "test")
+	assert.Equal(t, cmd, deployment.Spec.Template.Spec.Containers[0].Command)
+
+	// When one of the ExtraCommandArgs already exists in cmd with same or different value
+	a.Spec.Repo.ExtraRepoCommandArgs = []string{
+		"--redis",
+		"foo.scv.cluster.local:6379",
+	}
+
+	assert.NoError(t, r.reconcileRepoDeployment(a, false))
+	assert.NoError(t, r.Client.Get(
+		context.TODO(),
+		types.NamespacedName{
+			Name:      "argocd-repo-server",
+			Namespace: a.Namespace,
+		},
+		deployment))
+
+	assert.Equal(t, baseCommand, deployment.Spec.Template.Spec.Containers[0].Command)
+
+	// Remove all the command arguments that were added.
+	a.Spec.Repo.ExtraRepoCommandArgs = []string{}
+
+	assert.NoError(t, r.reconcileRepoDeployment(a, false))
+	assert.NoError(t, r.Client.Get(
+		context.TODO(),
+		types.NamespacedName{
+			Name:      "argocd-repo-server",
+			Namespace: a.Namespace,
+		},
+		deployment))
+
+	assert.Equal(t, baseCommand, deployment.Spec.Template.Spec.Containers[0].Command)
+}
diff --git a/controllers/argocd/prometheus_test.go b/controllers/argocd/prometheus_test.go
new file mode 100644
index 00000000..31a47cc2
--- /dev/null
+++ b/controllers/argocd/prometheus_test.go
@@ -0,0 +1,201 @@
+package argocd
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	argoprojv1alpha1 "github.com/argoproj-labs/argocd-operator/api/v1alpha1"
+	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
+	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+func TestReconcileWorkloadStatusAlertRule(t *testing.T) {
+	tests := []struct {
+		name              string
+		argocd            *argoprojv1alpha1.ArgoCD
+		wantPromRuleFound bool
+		existingPromRule  bool
+	}{
+		{
+			name: "monitoring enabled, no existing prom rule",
+			argocd: makeTestArgoCD(func(cr *argoprojv1alpha1.ArgoCD) {
+				cr.Spec.Monitoring.Enabled = true
+			}),
+			existingPromRule:  false,
+			wantPromRuleFound: true,
+		},
+		{
+			name: "monitoring disabled, no existing prom rule",
+			argocd: makeTestArgoCD(func(cr *argoprojv1alpha1.ArgoCD) {
+				cr.Spec.Monitoring.Enabled = false
+			}),
+			existingPromRule:  false,
+			wantPromRuleFound: false,
+		},
+		{
+			name: "monitoring enabled, existing prom rule",
+			argocd: makeTestArgoCD(func(cr *argoprojv1alpha1.ArgoCD) {
+				cr.Spec.Monitoring.Enabled = true
+			}),
+			existingPromRule:  true,
+			wantPromRuleFound: true,
+		},
+		{
+			name: "monitoring disabled, existing prom rule",
+			argocd: makeTestArgoCD(func(cr *argoprojv1alpha1.ArgoCD) {
+				cr.Spec.Monitoring.Enabled = false
+			}),
+			existingPromRule:  true,
+			wantPromRuleFound: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+
+			desiredRuleGroup := []monitoringv1.RuleGroup{
+				{
+					Name: "ArgoCDComponentStatus",
+					Rules: []monitoringv1.Rule{
+						{
+							Alert: "ApplicationControllerNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("application controller deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_statefulset_status_replicas{statefulset=\"%s\", namespace=\"%s\"} != kube_statefulset_status_replicas_ready{statefulset=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-application-controller"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-application-controller"), test.argocd.Namespace),
+							},
+							For: "1m",
+							Labels: map[string]string{
+								"severity": "critical",
+							},
+						},
+						{
+							Alert: "ServerNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("server deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-server"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-server"), test.argocd.Namespace),
+							},
+							For: "1m",
+							Labels: map[string]string{
+								"severity": "critical",
+							},
+						},
+						{
+							Alert: "RepoServerNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("repo server deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-repo-server"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-repo-server"), test.argocd.Namespace),
+							},
+							For: "1m",
+							Labels: map[string]string{
+								"severity": "critical",
+							},
+						},
+						{
+							Alert: "ApplicationSetControllerNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("applicationSet controller deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-applicationset-controller"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-applicationset-controller"), test.argocd.Namespace),
+							},
+							For: "5m",
+							Labels: map[string]string{
+								"severity": "warning",
+							},
+						},
+						{
+							Alert: "DexNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("dex deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-dex-server"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-dex-server"), test.argocd.Namespace),
+							},
+							For: "5m",
+							Labels: map[string]string{
+								"severity": "warning",
+							},
+						},
+						{
+							Alert: "NotificationsControllerNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("notifications controller deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-notifications-controller"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-notifications-controller"), test.argocd.Namespace),
+							},
+							For: "5m",
+							Labels: map[string]string{
+								"severity": "warning",
+							},
+						},
+						{
+							Alert: "RedisNotReady",
+							Annotations: map[string]string{
+								"message": fmt.Sprintf("redis deployment for Argo CD instance in namespace %s is not running", test.argocd.Namespace),
+							},
+							Expr: intstr.IntOrString{
+								Type:   intstr.String,
+								StrVal: fmt.Sprintf("kube_deployment_status_replicas{deployment=\"%s\", namespace=\"%s\"} != kube_deployment_status_replicas_ready{deployment=\"%s\", namespace=\"%s\"} ", fmt.Sprintf(test.argocd.Name+"-redis"), test.argocd.Namespace, fmt.Sprintf(test.argocd.Name+"-redis"), test.argocd.Namespace),
+							},
+							For: "5m",
+							Labels: map[string]string{
+								"severity": "warning",
+							},
+						},
+					},
+				},
+			}
+
+			r := makeTestReconciler(t, test.argocd)
+			err := monitoringv1.AddToScheme(r.Scheme)
+			assert.NoError(t, err)
+
+			if test.existingPromRule {
+				r.Client.Create(context.TODO(), newPrometheusRule(test.argocd.Namespace, "argocd-component-status-alert"))
+			}
+
+			err = r.reconcilePrometheusRule(test.argocd)
+
+			// reconciler doesn't need to do anything and should return nil
+			if (test.existingPromRule && test.wantPromRuleFound) || (!test.existingPromRule && !test.wantPromRuleFound) {
+				if err != nil {
+					t.Fatal("expected nil response but got non-nil response")
+				}
+			} else {
+				// reconciler either needs to create rule or delete it
+				testRule := &monitoringv1.PrometheusRule{}
+				err = r.Client.Get(context.TODO(), types.NamespacedName{
+					Name:      "argocd-component-status-alert",
+					Namespace: test.argocd.Namespace,
+				}, testRule)
+
+				if test.wantPromRuleFound && err != nil {
+					t.Fatal("unexpected error - prometheusRule not found")
+				} else if !test.wantPromRuleFound && err == nil {
+					t.Fatal("expected error but did not get one - prometheusRule not deleted")
+				}
+
+				if !test.existingPromRule {
+					assert.Equal(t, desiredRuleGroup, testRule.Spec.Groups)
+				}
+
+			}
+		})
+	}
+}
diff --git a/tests/k8s/1-028_validate_extra_repo_commands_args/01-assert.yaml b/tests/k8s/1-028_validate_extra_repo_commands_args/01-assert.yaml
new file mode 100644
index 00000000..b3b5cbd3
--- /dev/null
+++ b/tests/k8s/1-028_validate_extra_repo_commands_args/01-assert.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+  namespace: test-1-28-repo-extra-command
+
+status:
+  phase: Available
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: example-argocd-repo-server
+  namespace: test-1-28-repo-extra-command
+status:
+  readyReplicas: 1
\ No newline at end of file
diff --git a/tests/k8s/1-028_validate_extra_repo_commands_args/01-basic-argocd-install.yaml b/tests/k8s/1-028_validate_extra_repo_commands_args/01-basic-argocd-install.yaml
new file mode 100644
index 00000000..4fc777b6
--- /dev/null
+++ b/tests/k8s/1-028_validate_extra_repo_commands_args/01-basic-argocd-install.yaml
@@ -0,0 +1,14 @@
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: test-1-28-repo-extra-command
+---
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+  namespace: test-1-28-repo-extra-command
+  labels:
+    example: basic
+spec: {}
\ No newline at end of file
diff --git a/tests/k8s/1-028_validate_extra_repo_commands_args/02-add-extraRepoCommandArgs.yaml b/tests/k8s/1-028_validate_extra_repo_commands_args/02-add-extraRepoCommandArgs.yaml
new file mode 100644
index 00000000..858fc792
--- /dev/null
+++ b/tests/k8s/1-028_validate_extra_repo_commands_args/02-add-extraRepoCommandArgs.yaml
@@ -0,0 +1,10 @@
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+  namespace: test-1-28-repo-extra-command
+spec:
+  repo:
+    extraRepoCommandArgs:
+      - --reposerver.max.combined.directory.manifests.size
+      - 10M
\ No newline at end of file
diff --git a/tests/k8s/1-028_validate_extra_repo_commands_args/02-assert.yaml b/tests/k8s/1-028_validate_extra_repo_commands_args/02-assert.yaml
new file mode 100644
index 00000000..d9e6cdc1
--- /dev/null
+++ b/tests/k8s/1-028_validate_extra_repo_commands_args/02-assert.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: example-argocd-repo-server
+  namespace: test-1-28-repo-extra-command
+spec:
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: example-argocd-repo-server
+  template:
+    spec:
+      containers:
+        - command:
+            - uid_entrypoint.sh
+            - argocd-repo-server
+            - --redis
+            - example-argocd-redis.test-1-28-repo-extra-command.svc.cluster.local:6379
+            - --loglevel
+            - info
+            - --logformat
+            - text
+            - --reposerver.max.combined.directory.manifests.size
+            - 10M
+          name: argocd-repo-server
+
diff --git a/tests/k8s/1-028_validate_extra_repo_commands_args/99-delete.yaml b/tests/k8s/1-028_validate_extra_repo_commands_args/99-delete.yaml
new file mode 100644
index 00000000..16c51d21
--- /dev/null
+++ b/tests/k8s/1-028_validate_extra_repo_commands_args/99-delete.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+delete:
+- apiVersion: v1
+  kind: Namespace
+  name: test-1-28-repo-extra-command
\ No newline at end of file