Repository: argoproj/argo-cd
Pull request #8391 (2022-02-05 00:15:32)

fix: refreshing label is hidden by resource tree
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

before:

https://user-images.githubusercontent.com/426437/152619636-03def924-0310-4298-9f18-0aae9e89981b.mov

after

https://user-images.githubusercontent.com/426437/152619643-3a97fa82-8449-4a89-9752-f17112ecdb74.mov





Patch:
diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
index 6c3cd8668..3646323dc 100644
--- a/.github/workflows/ci-build.yaml
+++ b/.github/workflows/ci-build.yaml
@@ -331,7 +331,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        k3s-version: [v1.21.2, v1.20.2, v1.19.2, v1.18.9, v1.17.11]
+        k3s-version: [v1.23.3, v1.22.6, v1.21.2]
     needs: 
       - build-go
     env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a4cc9fbe..58f9c5608 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,154 @@
 # Changelog
 
+## v2.3.0 (Unreleased)
+
+### Argo CD ApplicationSet and Notifications are now part of Argo CD
+
+Two popular [Argoproj Labs](https://github.com/argoproj-labs) projects [Argo CD ApplicationSet](https://github.com/argoproj/applicationset) and
+[Argo CD Notifications](https://github.com/argoproj-labs/argocd-notifications) are now part of Argo CD! The default Argo CD installation manifests now
+bundle both projects out of the box. Going forward you can expect more tightened integration of these projects into Argo CD.
+
+### New sync and diff strategies
+
+Users can now configure the Application resource to instruct Argo CD to consider the ignore difference setup during the sync process.
+In order to do so, add the new sync option RespectIgnoreDifferences=true in the Application resource. Once the sync option is added,
+Argo CD won't change ignored fields during the syncing process.
+
+Configuring ignored fields is also easier now. Instead of listing fields one by one users can now leverage the 
+managedFields metadata to instruct Argo CD about trusted managers and automatically ignore any fields owned by them. A new diff customization
+(managedFieldsManagers) is now available allowing users to specify managers the application should trust and to ignore all fields owned by those managers.
+Read more about these changes at [New sync and diff strategies in ArgoCD](https://blog.argoproj.io/new-sync-and-diff-strategies-in-argocd-44195d3f8b8c) blog post.
+
+### ARM Images
+
+An officially supported ARM 64 image is now available. Enjoy running Argo CD on your Raspberry Pi! Additionally, the image size was reduced by nearly ~50%
+and is only 200MB now. The ARM version of `argocd` CLI is also available and published as a Github release artifact.
+
+### Compact Tree View And Click Application Navigation
+
+The application details page now supports compact application resources tree visualization. Using the "Group Nodes" button, you can collapse the similar resources
+into a single group node to remove the clutter and make it easier to understand the state of application resources. You still can get detailed information about the collapsed resources by clicking on the group node. The list of collapsed resources will be available in a sliding panel. Compact resource tree is still too big?
+You can use the zoom in and zoom out feature to make it smaller - or even larger!
+
+You no longer need to move back and forth between the application details page and the application list page. Instead you can navigate directly to the required application by clicking the search icon in the application details page title.
+
+### Upgraded Config Management Tools
+
+Both bundled Helm and Kustomize binaries have been upgraded to the latest versions. Kustomize has been upgraded from 4.2.0 to 4.4.1 and Helm has been upgraded from 3.7.1 to 3.8.0.
+
+### Bug Fixes and Performance Enhancements
+
+* Config management tools enhancements:
+* The skipCrds flag and ability to ignore missing values files for Helm (#8012, #8003)
+* Additional environment variables for Kustomize (#8096)
+* Argo CD CLI follows the XDG Base directory standard (#7638)
+* Redis is no longer used during SSO login (#8241)
+
+
+### Features
+
+- feat: Add app list and details page views to navigation history (#7776) (#7937)
+- feat: Add skipCrds flag for helm charts (#8012)
+- feat: Add visual indicator for newly created pods (#8006)
+- feat: Added a new Helm option ignoreMissingValueFiles (#7767) (#8003)
+- feat: Allow configuring system wide ignore differences for all resources (#8224)
+- feat: Allow escaping dollar in Envsubst (#7961)
+- feat: Allow external links on Application (#3487) (#8231)
+- feat: Allow selecting application on detail page (#8176)
+- feat: Bundle applicationset-controller with argocd (#8148)
+- feat: Enable specifying root ca for oidc (#6712)
+- feat: Expose ARGOCD_APP_NAME to the `kustomize build` command (#8096)
+- feat: Ignore differences owned by trusted managers from managedFields (#7869)
+- feat: New sync option to use ignore diff configs during sync (#8078)
+- feat: Provide address flag for admin dashboard command (#8095)
+- feat: Store "Group Nodes" button state in application details preferences (#8036)
+- feat: Support specifying cluster by name in addition to API server URL in Cluster API (#8077)
+- feat: Support XDG Base directory standard (#7638) (#7791)
+- feat: Use encrypted cookie to store OAuth2 state nonce (instead of redis) (#8241)
+- feat: Build images on PR and conditionally build arm64 image on push (#8108)
+
+### Bug Fixes
+
+- fix: Add "Restarting MinIO" status to MiniO Tenant health check (#8191)
+- fix: Add all resources in list view (#7295)
+- fix: Adding pagination to grouped nodes sliding panel#7837 (#7915)
+- fix: Allow all resources to add external links (#7923)
+- fix: Always call ValidateDestination (#7976)
+- fix: Application exist panic when execute api call (#8188)
+- fix: Application-icons-alignment (#8054)
+- fix: Controller panics if resource manifest has incorrect annotation (#8022)
+- fix: Correctly handle project field during partial cluster update (#7994)
+- fix: Default value for retry validation #8055 (#8064)
+- fix: Fix a possible crash when parsing RBAC (#8165)
+- fix: Grouped node list missing resources on Compact resources view #8014 (#8018)
+- fix: Issue with headless installation (#7958)
+- fix: Issue with project scoped resources (#8048)
+- fix: Kubernetes labels normalization for Prometheus  (#7925)
+- fix: Nested Refresh dropdown does not work on Application Details page #1524 (#7950)
+- fix: Network line colors and menu icon alignment (#8059)
+- fix: Opening app details shows UI error on some apps (#8016) (#8019)
+- fix: Parse to correct uint32 type (#8177)
+- fix: Prevent possible nil-pointer deref in normalizer (#8185)
+- fix: Prevent possible out-of-bounds access when loading policies (#8186)
+- fix: Provide a semantic version parsed version for KUBE_VERSION (#8250)
+- fix: Refreshing label toast (#7979)
+- fix: Resource details page crashes when resource is not deployed and hide managed fields is selected (#7971)
+- fix: Retry disabled text (#8004)
+- fix: Route health check stuck in 'Progressing' (#8170)
+- fix: Sync window panel is crashed if resource name not contain letters (#8053)
+- fix: Targetervision compatible without prefix refs/heads or refs/tags (#7939)
+- fix: Trailing line in Filter Dropdown Menus #7821 (#8001)
+- fix: Webhook URL matching edge cases (#7981)
+- fix(ui): Use consistent case for diff modes (#7945)
+- fix: Use gRPC timeout for sidecar CMPs (#8131) (#8236)
+
+### Other
+
+- chore: Bump go-jsonnet to v0.18.0 (#8011)
+- chore: Escape proj in regex (#7985)
+- chore: Exclude argocd-server rbac for core-install (#8234)
+- chore: Log out the resource triggering reconciliation (#8192)
+- chore: Migrate to use golang-jwt/jwt v4.2.0 (#8136)
+- chore: Move resolveRevision from api-server to repo-server (#7966)
+- chore: Update notifications version (#8267)
+- chore: Update slack version (#8299)
+- chore: Update to Redis 6.2.4 (#8157)
+- chore: Upgrade awscli to 2.4.6 and remove python deps (#7947)
+- chore: Upgrade base image to ubuntu:21.10 (#8230)
+- chore: Upgrade dex to v2.30.2 (https://github.com/dexidp/dex/issues/2326) (#8237)
+- chore: Upgrade gitops engine (#8288)
+- chore: Upgrade golang to 1.17.6 (#8229)
+- chore: Upgrade helm to most recent version (v3.7.2) (#8226)
+- chore: Upgrade k8s client to v1.23 (#8213)
+- chore: Upgrade kustomize to most recent version (v4.4.1) (#8227)
+- refactor: Introduce 'byClusterName' secret index to speedup cluster server URL lookup (#8133)
+- refactor: Move project filtering to server side (#8102)
+
+## v2.2.3 (2022-01-18)
+
+- fix: Application exist panic when execute api call (#8188)
+- fix: Route health check stuck in 'Progressing' (#8170)
+- refactor: Introduce 'byClusterName' secret index to speedup cluster server URL lookup (#8133)
+- chore: Update to Redis 6.2.4 (#8157) (#8158)
+
+## v2.2.2 (2021-12-31)
+
+- fix: Issue with project scoped resources (#8048)
+- fix: Escape proj in regex (#7985)
+- fix: Default value for retry validation #8055 (#8064)
+- fix: Sync window panel is crashed if resource name not contain letters (#8053)
+- fix: Upgrade github.com/argoproj/gitops-engine to v0.5.2
+- fix: Retry disabled text (#8004)
+- fix: Opening app details shows UI error on some apps (#8016) (#8019)
+- fix: Correctly handle project field during partial cluster update (#7994)
+- fix: Cluster API does not support updating labels and annotations (#7901)
+
+## v2.2.1 (2021-12-16)
+
+- fix: Resource details page crashes when resource is not deployed and hide managed fields is selected (#7971)
+- fix: Issue with headless installation (#7958)
+- fix: Nil pointer (#7905)
+
 ## v2.2.0 (2021-12-14)
 
 > [Upgrade instructions](./docs/operator-manual/upgrading/2.1-2.2.md)
diff --git a/Makefile b/Makefile
index fdbef3c57..1a4563ae0 100644
--- a/Makefile
+++ b/Makefile
@@ -179,7 +179,7 @@ gogen: ensure-gopath
 	go generate ./util/argo/...
 
 .PHONY: protogen
-protogen: ensure-gopath
+protogen: ensure-gopath mod-vendor-local
 	export GO111MODULE=off
 	./hack/generate-proto.sh
 
diff --git a/USERS.md b/USERS.md
index cd907d02d..f69f8ae07 100644
--- a/USERS.md
+++ b/USERS.md
@@ -83,6 +83,7 @@ Currently, the following organizations are **officially** using Argo CD:
 1. [Kinguin](https://www.kinguin.net/)
 1. [KintoHub](https://www.kintohub.com/)
 1. [KompiTech GmbH](https://www.kompitech.com/)
+1. [KubeSphere](https://github.com/kubesphere)
 1. [LexisNexis](https://www.lexisnexis.com/)
 1. [LINE](https://linecorp.com/en/)
 1. [Lytt](https://www.lytt.co/)
diff --git a/assets/swagger.json b/assets/swagger.json
index fff9328b2..9dfe2b138 100644
--- a/assets/swagger.json
+++ b/assets/swagger.json
@@ -3733,6 +3733,9 @@
         "statusBadgeEnabled": {
           "type": "boolean"
         },
+        "statusBadgeRootUrl": {
+          "type": "string"
+        },
         "trackingMethod": {
           "type": "string"
         },
diff --git a/cmd/argocd-server/commands/argocd_server.go b/cmd/argocd-server/commands/argocd_server.go
index 13126c0dd..e02816688 100644
--- a/cmd/argocd-server/commands/argocd_server.go
+++ b/cmd/argocd-server/commands/argocd_server.go
@@ -96,7 +96,7 @@ func NewCommand() *cobra.Command {
 			if failureRetryCount > 0 {
 				appclientsetConfig = kube.AddFailureRetryWrapper(appclientsetConfig, failureRetryCount, failureRetryPeriodMilliSeconds)
 			}
-			appclientset := appclientset.NewForConfigOrDie(appclientsetConfig)
+			appClientSet := appclientset.NewForConfigOrDie(appclientsetConfig)
 			tlsConfig := apiclient.TLSConfiguration{
 				DisableTLS:       repoServerPlaintext,
 				StrictValidation: repoServerStrictTLS,
@@ -131,7 +131,7 @@ func NewCommand() *cobra.Command {
 				BaseHRef:            baseHRef,
 				RootPath:            rootPath,
 				KubeClientset:       kubeclientset,
-				AppClientset:        appclientset,
+				AppClientset:        appClientSet,
 				RepoClientset:       repoclientset,
 				DexServerAddr:       dexServerAddress,
 				DisableAuth:         disableAuth,
diff --git a/cmd/argocd/commands/account.go b/cmd/argocd/commands/account.go
index 0c87f0d32..419ac24b3 100644
--- a/cmd/argocd/commands/account.go
+++ b/cmd/argocd/commands/account.go
@@ -16,6 +16,7 @@ import (
 	"github.com/spf13/cobra"
 	"golang.org/x/term"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	accountpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
 	"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
@@ -72,7 +73,7 @@ has appropriate RBAC permissions to change other accounts.
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			acdClient := argocdclient.NewClientOrDie(clientOpts)
+			acdClient := headless.NewClientOrDie(clientOpts, c)
 			conn, usrIf := acdClient.NewAccountClientOrDie()
 			defer io.Close(conn)
 
@@ -146,7 +147,7 @@ func NewAccountGetUserInfoCommand(clientOpts *argocdclient.ClientOptions) *cobra
 				os.Exit(1)
 			}
 
-			conn, client := argocdclient.NewClientOrDie(clientOpts).NewSessionClientOrDie()
+			conn, client := headless.NewClientOrDie(clientOpts, c).NewSessionClientOrDie()
 			defer io.Close(conn)
 
 			ctx := context.Background()
@@ -201,7 +202,7 @@ Resources: %v
 				os.Exit(1)
 			}
 
-			conn, client := argocdclient.NewClientOrDie(clientOpts).NewAccountClientOrDie()
+			conn, client := headless.NewClientOrDie(clientOpts, c).NewAccountClientOrDie()
 			defer io.Close(conn)
 
 			ctx := context.Background()
@@ -241,7 +242,7 @@ func NewAccountListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
 		Example: "argocd account list",
 		Run: func(c *cobra.Command, args []string) {
 
-			conn, client := argocdclient.NewClientOrDie(clientOpts).NewAccountClientOrDie()
+			conn, client := headless.NewClientOrDie(clientOpts, c).NewAccountClientOrDie()
 			defer io.Close(conn)
 
 			ctx := context.Background()
@@ -287,7 +288,7 @@ argocd account get
 # Get details for an account by name
 argocd account get --account <account-name>`,
 		Run: func(c *cobra.Command, args []string) {
-			clientset := argocdclient.NewClientOrDie(clientOpts)
+			clientset := headless.NewClientOrDie(clientOpts, c)
 
 			if account == "" {
 				account = getCurrentAccount(clientset).Username
@@ -359,7 +360,7 @@ argocd account generate-token
 argocd account generate-token --account <account-name>`,
 		Run: func(c *cobra.Command, args []string) {
 
-			clientset := argocdclient.NewClientOrDie(clientOpts)
+			clientset := headless.NewClientOrDie(clientOpts, c)
 			conn, client := clientset.NewAccountClientOrDie()
 			defer io.Close(conn)
 			if account == "" {
@@ -401,7 +402,7 @@ argocd account delete-token --account <account-name> ID`,
 			}
 			id := args[0]
 
-			clientset := argocdclient.NewClientOrDie(clientOpts)
+			clientset := headless.NewClientOrDie(clientOpts, c)
 			conn, client := clientset.NewAccountClientOrDie()
 			defer io.Close(conn)
 			if account == "" {
diff --git a/cmd/argocd/commands/admin/app.go b/cmd/argocd/commands/admin/app.go
index 97c989fb0..802ad7b33 100644
--- a/cmd/argocd/commands/admin/app.go
+++ b/cmd/argocd/commands/admin/app.go
@@ -10,8 +10,6 @@ import (
 	"sort"
 	"time"
 
-	"github.com/argoproj/argo-cd/v2/util/argo"
-
 	"github.com/ghodss/yaml"
 	"github.com/spf13/cobra"
 	apiv1 "k8s.io/api/core/v1"
@@ -29,7 +27,8 @@ import (
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
 	appinformers "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
-	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
+	argocdclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
+	"github.com/argoproj/argo-cd/v2/util/argo"
 	cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
 	appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
 	"github.com/argoproj/argo-cd/v2/util/cli"
@@ -263,7 +262,7 @@ func NewReconcileCommand() *cobra.Command {
 					errors.CheckError(err)
 					repoServerAddress = fmt.Sprintf("localhost:%d", repoServerPort)
 				}
-				repoServerClient := apiclient.NewRepoServerClientset(repoServerAddress, 60, apiclient.TLSConfiguration{DisableTLS: false, StrictValidation: false})
+				repoServerClient := argocdclient.NewRepoServerClientset(repoServerAddress, 60, argocdclient.TLSConfiguration{DisableTLS: false, StrictValidation: false})
 
 				appClientset := appclientset.NewForConfigOrDie(cfg)
 				kubeClientset := kubernetes.NewForConfigOrDie(cfg)
@@ -327,7 +326,7 @@ func reconcileApplications(
 	kubeClientset kubernetes.Interface,
 	appClientset appclientset.Interface,
 	namespace string,
-	repoServerClient apiclient.Clientset,
+	repoServerClient argocdclient.Clientset,
 	selector string,
 	createLiveStateCache func(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache,
 ) ([]appReconcileResult, error) {
diff --git a/cmd/argocd/commands/admin/dashboard.go b/cmd/argocd/commands/admin/dashboard.go
index abb0552bd..3117653b7 100644
--- a/cmd/argocd/commands/admin/dashboard.go
+++ b/cmd/argocd/commands/admin/dashboard.go
@@ -7,8 +7,10 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
 	"github.com/argoproj/argo-cd/v2/common"
-	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
+	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
+	"github.com/argoproj/argo-cd/v2/util/errors"
 )
 
 func NewDashboardCommand() *cobra.Command {
@@ -20,12 +22,12 @@ func NewDashboardCommand() *cobra.Command {
 		Use:   "dashboard",
 		Short: "Starts Argo CD Web UI locally",
 		Run: func(cmd *cobra.Command, args []string) {
+			errors.CheckError(headless.StartLocalServer(&argocdclient.ClientOptions{Core: true}, initialize.RetrieveContextIfChanged(cmd.Flag("context")), &port, &address))
 			println(fmt.Sprintf("Argo CD UI is available at http://%s:%d", address, port))
 			<-context.Background().Done()
 		},
 	}
-	clientOpts := &apiclient.ClientOptions{Core: true}
-	headless.InitCommand(cmd, clientOpts, &port, &address)
+	initialize.InitCommand(cmd)
 	cmd.Flags().IntVar(&port, "port", common.DefaultPortAPIServer, "Listen on given port")
 	cmd.Flags().StringVar(&address, "address", common.DefaultAddressAPIServer, "Listen on given address")
 	return cmd
diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go
index 89398f55b..c1d4c98bd 100644
--- a/cmd/argocd/commands/app.go
+++ b/cmd/argocd/commands/app.go
@@ -14,9 +14,8 @@ import (
 	"time"
 	"unicode/utf8"
 
-	"github.com/argoproj/gitops-engine/pkg/sync/common"
-
 	"github.com/argoproj/gitops-engine/pkg/health"
+	"github.com/argoproj/gitops-engine/pkg/sync/common"
 	"github.com/argoproj/gitops-engine/pkg/sync/hook"
 	"github.com/argoproj/gitops-engine/pkg/sync/ignore"
 	"github.com/argoproj/gitops-engine/pkg/utils/kube"
@@ -31,9 +30,9 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/utils/pointer"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
 	"github.com/argoproj/argo-cd/v2/controller"
-	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
 	applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
@@ -133,7 +132,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
 	argocd app create ksane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane
 `,
 		Run: func(c *cobra.Command, args []string) {
-			argocdClient := argocdclient.NewClientOrDie(clientOpts)
+			argocdClient := headless.NewClientOrDie(clientOpts, c)
 
 			apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags())
 			errors.CheckError(err)
@@ -215,14 +214,14 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			acdClient := argocdclient.NewClientOrDie(clientOpts)
+			acdClient := headless.NewClientOrDie(clientOpts, c)
 			conn, appIf := acdClient.NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			appName := args[0]
 			app, err := appIf.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName, Refresh: getRefreshType(refresh, hardRefresh)})
 			errors.CheckError(err)
 
-			pConn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			pConn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(pConn)
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: app.Spec.Project})
 			errors.CheckError(err)
@@ -292,7 +291,7 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			acdClient := argocdclient.NewClientOrDie(clientOpts)
+			acdClient := headless.NewClientOrDie(clientOpts, c)
 			conn, appIf := acdClient.NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			appName := args[0]
@@ -531,7 +530,7 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
 			}
 			ctx := context.Background()
 			appName := args[0]
-			argocdClient := argocdclient.NewClientOrDie(clientOpts)
+			argocdClient := headless.NewClientOrDie(clientOpts, c)
 			conn, appIf := argocdClient.NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
@@ -587,7 +586,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
 				os.Exit(1)
 			}
 			appName := args[0]
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			app, err := appIf.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName})
 			errors.CheckError(err)
@@ -746,9 +745,9 @@ func liveObjects(resources []*argoappv1.ResourceDiff) ([]*unstructured.Unstructu
 	return objs, nil
 }
 
-func getLocalObjects(app *argoappv1.Application, local, localRepoRoot, appLabelKey, kubeVersion string, kustomizeOptions *argoappv1.KustomizeOptions,
+func getLocalObjects(app *argoappv1.Application, local, localRepoRoot, appLabelKey, kubeVersion string, apiVersions []string, kustomizeOptions *argoappv1.KustomizeOptions,
 	configManagementPlugins []*argoappv1.ConfigManagementPlugin, trackingMethod string) []*unstructured.Unstructured {
-	manifestStrings := getLocalObjectsString(app, local, localRepoRoot, appLabelKey, kubeVersion, kustomizeOptions, configManagementPlugins, trackingMethod)
+	manifestStrings := getLocalObjectsString(app, local, localRepoRoot, appLabelKey, kubeVersion, apiVersions, kustomizeOptions, configManagementPlugins, trackingMethod)
 	objs := make([]*unstructured.Unstructured, len(manifestStrings))
 	for i := range manifestStrings {
 		obj := unstructured.Unstructured{}
@@ -759,7 +758,7 @@ func getLocalObjects(app *argoappv1.Application, local, localRepoRoot, appLabelK
 	return objs
 }
 
-func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, appLabelKey, kubeVersion string, kustomizeOptions *argoappv1.KustomizeOptions,
+func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, appLabelKey, kubeVersion string, apiVersions []string, kustomizeOptions *argoappv1.KustomizeOptions,
 	configManagementPlugins []*argoappv1.ConfigManagementPlugin, trackingMethod string) []string {
 
 	res, err := repository.GenerateManifests(context.Background(), local, localRepoRoot, app.Spec.Source.TargetRevision, &repoapiclient.ManifestRequest{
@@ -770,6 +769,7 @@ func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, app
 		ApplicationSource: &app.Spec.Source,
 		KustomizeOptions:  kustomizeOptions,
 		KubeVersion:       kubeVersion,
+		ApiVersions:       apiVersions,
 		Plugins:           configManagementPlugins,
 		TrackingMethod:    trackingMethod,
 	}, true)
@@ -835,7 +835,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				os.Exit(2)
 			}
 
-			clientset := argocdclient.NewClientOrDie(clientOpts)
+			clientset := headless.NewClientOrDie(clientOpts, c)
 			conn, appIf := clientset.NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			appName := args[0]
@@ -857,7 +857,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				defer argoio.Close(conn)
 				cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
 				errors.CheckError(err)
-				localObjs := groupObjsByKey(getLocalObjects(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod), liveObjs, app.Spec.Destination.Namespace)
+				localObjs := groupObjsByKey(getLocalObjects(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.Info.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod), liveObjs, app.Spec.Destination.Namespace)
 				items = groupObjsForDiff(resources, localObjs, items, argoSettings, appName)
 			} else if revision != "" {
 				var unstructureds []*unstructured.Unstructured
@@ -995,7 +995,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			var isTerminal bool = isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
 			var isConfirmAll bool = false
@@ -1108,7 +1108,7 @@ func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
   # List apps by label, in this example we listing apps that are children of another app (aka app-of-apps)
   argocd app list -l app.kubernetes.io/instance=my-app`,
 		Run: func(c *cobra.Command, args []string) {
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			apps, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{Selector: selector})
 			errors.CheckError(err)
@@ -1248,7 +1248,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 			}
 			selectedResources := parseSelectedResources(resources)
 			appNames := args
-			acdClient := argocdclient.NewClientOrDie(clientOpts)
+			acdClient := headless.NewClientOrDie(clientOpts, c)
 			closer, appIf := acdClient.NewApplicationClientOrDie()
 			defer argoio.Close(closer)
 			if selector != "" {
@@ -1327,7 +1327,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			acdClient := argocdclient.NewClientOrDie(clientOpts)
+			acdClient := headless.NewClientOrDie(clientOpts, c)
 			conn, appIf := acdClient.NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 
@@ -1402,7 +1402,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 					cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
 					errors.CheckError(err)
 					argoio.Close(conn)
-					localObjsStrings = getLocalObjectsString(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod)
+					localObjsStrings = getLocalObjectsString(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.Info.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod)
 				}
 
 				syncOptionsFactory := func() *applicationpkg.SyncOptions {
@@ -1621,7 +1621,7 @@ func checkResourceStatus(watchSync bool, watchHealth bool, watchOperation bool,
 
 const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n"
 
-func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, selectedResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
+func waitOnApplicationStatus(acdClient argocdclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, selectedResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
@@ -1842,7 +1842,7 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			appName := args[0]
 			app, err := appIf.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName})
@@ -1896,7 +1896,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
 				depID, err = strconv.Atoi(args[1])
 				errors.CheckError(err)
 			}
-			acdClient := argocdclient.NewClientOrDie(clientOpts)
+			acdClient := headless.NewClientOrDie(clientOpts, c)
 			conn, appIf := acdClient.NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			ctx := context.Background()
@@ -1963,7 +1963,7 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
 				os.Exit(1)
 			}
 			appName := args[0]
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			ctx := context.Background()
 			resources, err := appIf.ManagedResources(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &appName})
@@ -2021,7 +2021,7 @@ func NewApplicationTerminateOpCommand(clientOpts *argocdclient.ClientOptions) *c
 				os.Exit(1)
 			}
 			appName := args[0]
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			ctx := context.Background()
 			_, err := appIf.TerminateOperation(ctx, &applicationpkg.OperationTerminateRequest{Name: &appName})
@@ -2042,7 +2042,7 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				os.Exit(1)
 			}
 			appName := args[0]
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			app, err := appIf.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName})
 			errors.CheckError(err)
@@ -2087,7 +2087,7 @@ func NewApplicationListResourcesCommand(clientOpts *argocdclient.ClientOptions)
 			}
 			listAll := !c.Flag("orphaned").Changed
 			appName := args[0]
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 			appResourceTree, err := appIf.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &appName})
 			errors.CheckError(err)
@@ -2133,7 +2133,7 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
 				os.Exit(1)
 			}
 			appName := args[0]
-			conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 			defer argoio.Close(conn)
 
 			patchedApp, err := appIf.Patch(context.Background(), &applicationpkg.ApplicationPatchRequest{
@@ -2220,7 +2220,7 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
 		}
 		appName := args[0]
 
-		conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+		conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 		defer argoio.Close(conn)
 		ctx := context.Background()
 		resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
@@ -2276,7 +2276,7 @@ func NewApplicationDeleteResourceCommand(clientOpts *argocdclient.ClientOptions)
 		}
 		appName := args[0]
 
-		conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+		conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 		defer argoio.Close(conn)
 		ctx := context.Background()
 		resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
diff --git a/cmd/argocd/commands/app_actions.go b/cmd/argocd/commands/app_actions.go
index af0f36f89..a59e1d061 100644
--- a/cmd/argocd/commands/app_actions.go
+++ b/cmd/argocd/commands/app_actions.go
@@ -12,6 +12,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
 	"github.com/argoproj/argo-cd/v2/util/errors"
@@ -58,7 +59,7 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
 			os.Exit(1)
 		}
 		appName := args[0]
-		conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+		conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 		defer io.Close(conn)
 		ctx := context.Background()
 		resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
@@ -142,7 +143,7 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
 		appName := args[0]
 		actionName := args[1]
 
-		conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
+		conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
 		defer io.Close(conn)
 		ctx := context.Background()
 		resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
diff --git a/cmd/argocd/commands/cert.go b/cmd/argocd/commands/cert.go
index 3ab8f8ba5..a32e77ae7 100644
--- a/cmd/argocd/commands/cert.go
+++ b/cmd/argocd/commands/cert.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
 	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -64,7 +65,7 @@ func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 		Use:   "add-tls SERVERNAME",
 		Short: "Add TLS certificate data for connecting to repository server SERVERNAME",
 		Run: func(c *cobra.Command, args []string) {
-			conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
+			conn, certIf := headless.NewClientOrDie(clientOpts, c).NewCertClientOrDie()
 			defer io.Close(conn)
 
 			if len(args) != 1 {
@@ -147,7 +148,7 @@ func NewCertAddSSHCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 		Short: "Add SSH known host entries for repository servers",
 		Run: func(c *cobra.Command, args []string) {
 
-			conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
+			conn, certIf := headless.NewClientOrDie(clientOpts, c).NewCertClientOrDie()
 			defer io.Close(conn)
 
 			var sshKnownHostsLists []string
@@ -219,7 +220,7 @@ func NewCertRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
+			conn, certIf := headless.NewClientOrDie(clientOpts, c).NewCertClientOrDie()
 			defer io.Close(conn)
 			hostNamePattern := args[0]
 
@@ -274,7 +275,7 @@ func NewCertListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 				}
 			}
 
-			conn, certIf := argocdclient.NewClientOrDie(clientOpts).NewCertClientOrDie()
+			conn, certIf := headless.NewClientOrDie(clientOpts, c).NewCertClientOrDie()
 			defer io.Close(conn)
 			certificates, err := certIf.ListCertificates(context.Background(), &certificatepkg.RepositoryCertificateQuery{HostNamePattern: hostNamePattern, CertType: certType})
 			errors.CheckError(err)
diff --git a/cmd/argocd/commands/cluster.go b/cmd/argocd/commands/cluster.go
index 200ee9fea..1c32a6eae 100644
--- a/cmd/argocd/commands/cluster.go
+++ b/cmd/argocd/commands/cluster.go
@@ -9,23 +9,22 @@ import (
 	"text/tabwriter"
 
 	"github.com/mattn/go-isatty"
-	"k8s.io/client-go/kubernetes"
-
-	"github.com/argoproj/argo-cd/v2/util/cli"
-	"github.com/argoproj/argo-cd/v2/util/text/label"
-
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/tools/clientcmd"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
 	"github.com/argoproj/argo-cd/v2/common"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
 	argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/util/cli"
 	"github.com/argoproj/argo-cd/v2/util/clusterauth"
 	"github.com/argoproj/argo-cd/v2/util/errors"
 	"github.com/argoproj/argo-cd/v2/util/io"
+	"github.com/argoproj/argo-cd/v2/util/text/label"
 )
 
 // NewClusterCommand returns a new instance of an `argocd cluster` command
@@ -133,7 +132,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
 			annotationsMap, err := label.Parse(annotations)
 			errors.CheckError(err)
 
-			conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
+			conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
 			defer io.Close(conn)
 			if clusterOpts.Name != "" {
 				contextName = clusterOpts.Name
@@ -183,7 +182,7 @@ argocd cluster get in-cluster`,
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
+			conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
 			defer io.Close(conn)
 			clusters := make([]argoappv1.Cluster, 0)
 			for _, clusterSelector := range args {
@@ -252,7 +251,7 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
+			conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
 			defer io.Close(conn)
 
 			// clientset, err := kubernetes.NewForConfig(conf)
@@ -313,7 +312,7 @@ func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
 		Use:   "list",
 		Short: "List configured clusters",
 		Run: func(c *cobra.Command, args []string) {
-			conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
+			conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
 			defer io.Close(conn)
 			clusters, err := clusterIf.List(context.Background(), &clusterpkg.ClusterQuery{})
 			errors.CheckError(err)
@@ -345,7 +344,7 @@ func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
+			conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
 			defer io.Close(conn)
 			clusterQuery := clusterpkg.ClusterQuery{
 				Server: args[0],
diff --git a/cmd/argocd/commands/gpg.go b/cmd/argocd/commands/gpg.go
index feb7502f9..3cdc57992 100644
--- a/cmd/argocd/commands/gpg.go
+++ b/cmd/argocd/commands/gpg.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	gpgkeypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/gpgkey"
 	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -44,7 +45,7 @@ func NewGPGListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 		Use:   "list",
 		Short: "List configured GPG public keys",
 		Run: func(c *cobra.Command, args []string) {
-			conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie()
+			conn, gpgIf := headless.NewClientOrDie(clientOpts, c).NewGPGKeyClientOrDie()
 			defer argoio.Close(conn)
 			keys, err := gpgIf.List(context.Background(), &gpgkeypkg.GnuPGPublicKeyQuery{})
 			errors.CheckError(err)
@@ -75,7 +76,7 @@ func NewGPGGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 			if len(args) != 1 {
 				errors.CheckError(fmt.Errorf("Missing KEYID argument"))
 			}
-			conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie()
+			conn, gpgIf := headless.NewClientOrDie(clientOpts, c).NewGPGKeyClientOrDie()
 			defer argoio.Close(conn)
 			key, err := gpgIf.Get(context.Background(), &gpgkeypkg.GnuPGPublicKeyQuery{KeyID: args[0]})
 			errors.CheckError(err)
@@ -114,7 +115,7 @@ func NewGPGAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 			if err != nil {
 				errors.CheckError(err)
 			}
-			conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie()
+			conn, gpgIf := headless.NewClientOrDie(clientOpts, c).NewGPGKeyClientOrDie()
 			defer argoio.Close(conn)
 			resp, err := gpgIf.Create(context.Background(), &gpgkeypkg.GnuPGPublicKeyCreateRequest{Publickey: &appsv1.GnuPGPublicKey{KeyData: string(keyData)}})
 			errors.CheckError(err)
@@ -139,7 +140,7 @@ func NewGPGDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 			if len(args) != 1 {
 				errors.CheckError(fmt.Errorf("Missing KEYID argument"))
 			}
-			conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie()
+			conn, gpgIf := headless.NewClientOrDie(clientOpts, c).NewGPGKeyClientOrDie()
 			defer argoio.Close(conn)
 			_, err := gpgIf.Delete(context.Background(), &gpgkeypkg.GnuPGPublicKeyQuery{KeyID: args[0]})
 			errors.CheckError(err)
diff --git a/cmd/argocd/commands/headless/forward.go b/cmd/argocd/commands/headless/forward.go
deleted file mode 100644
index e9cb550e4..000000000
--- a/cmd/argocd/commands/headless/forward.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package headless
-
-import (
-	"context"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/go-redis/redis/v8"
-	"k8s.io/client-go/tools/clientcmd"
-
-	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
-	repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
-	"github.com/argoproj/argo-cd/v2/util/cache"
-	"github.com/argoproj/argo-cd/v2/util/io"
-	kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
-)
-
-type forwardCacheClient struct {
-	namespace string
-	context   string
-	init      sync.Once
-	client    cache.CacheClient
-	err       error
-}
-
-func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error {
-	c.init.Do(func() {
-		overrides := clientcmd.ConfigOverrides{
-			CurrentContext: c.context,
-		}
-		redisPort, err := kubeutil.PortForward(6379, c.namespace, &overrides,
-			"app.kubernetes.io/name=argocd-redis-ha-haproxy", "app.kubernetes.io/name=argocd-redis")
-		if err != nil {
-			c.err = err
-			return
-		}
-
-		redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)})
-		c.client = cache.NewRedisCache(redisClient, time.Hour)
-	})
-	if c.err != nil {
-		return c.err
-	}
-	return action(c.client)
-}
-
-func (c *forwardCacheClient) Set(item *cache.Item) error {
-	return c.doLazy(func(client cache.CacheClient) error {
-		return client.Set(item)
-	})
-}
-
-func (c *forwardCacheClient) Get(key string, obj interface{}) error {
-	return c.doLazy(func(client cache.CacheClient) error {
-		return client.Get(key, obj)
-	})
-}
-
-func (c *forwardCacheClient) Delete(key string) error {
-	return c.doLazy(func(client cache.CacheClient) error {
-		return client.Delete(key)
-	})
-}
-
-func (c *forwardCacheClient) OnUpdated(ctx context.Context, key string, callback func() error) error {
-	return c.doLazy(func(client cache.CacheClient) error {
-		return client.OnUpdated(ctx, key, callback)
-	})
-}
-
-func (c *forwardCacheClient) NotifyUpdated(key string) error {
-	return c.doLazy(func(client cache.CacheClient) error {
-		return client.NotifyUpdated(key)
-	})
-}
-
-type forwardRepoClientset struct {
-	namespace     string
-	context       string
-	init          sync.Once
-	repoClientset repoapiclient.Clientset
-	err           error
-}
-
-func (c *forwardRepoClientset) NewRepoServerClient() (io.Closer, repoapiclient.RepoServerServiceClient, error) {
-	c.init.Do(func() {
-		overrides := clientcmd.ConfigOverrides{
-			CurrentContext: c.context,
-		}
-		repoServerPort, err := kubeutil.PortForward(8081, c.namespace, &overrides, "app.kubernetes.io/name=argocd-repo-server")
-		if err != nil {
-			c.err = err
-			return
-		}
-		c.repoClientset = apiclient.NewRepoServerClientset(fmt.Sprintf("localhost:%d", repoServerPort), 60, apiclient.TLSConfiguration{
-			DisableTLS: false, StrictValidation: false})
-	})
-	if c.err != nil {
-		return nil, nil, c.err
-	}
-	return c.repoClientset.NewRepoServerClient()
-}
diff --git a/cmd/argocd/commands/headless/headless.go b/cmd/argocd/commands/headless/headless.go
index 152a86e54..720139109 100644
--- a/cmd/argocd/commands/headless/headless.go
+++ b/cmd/argocd/commands/headless/headless.go
@@ -5,35 +5,126 @@ import (
 	"fmt"
 	"net"
 	"os"
+	"sync"
 	"time"
 
+	"github.com/spf13/cobra"
+
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
+
 	"github.com/alicebob/miniredis/v2"
 	"github.com/go-redis/redis/v8"
 	"github.com/golang/protobuf/ptypes/empty"
 	log "github.com/sirupsen/logrus"
-	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/tools/cache"
+	cache2 "k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/utils/pointer"
 
-	argoapi "github.com/argoproj/argo-cd/v2/pkg/apiclient"
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
+	repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
 	"github.com/argoproj/argo-cd/v2/server"
 	servercache "github.com/argoproj/argo-cd/v2/server/cache"
-	cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
+	"github.com/argoproj/argo-cd/v2/util/cache"
 	appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
 	"github.com/argoproj/argo-cd/v2/util/cli"
 	"github.com/argoproj/argo-cd/v2/util/io"
+	kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
 	"github.com/argoproj/argo-cd/v2/util/localconfig"
-
-	flag "github.com/spf13/pflag"
 )
 
-func testAPI(clientOpts *argoapi.ClientOptions) error {
-	apiClient, err := argoapi.NewClient(clientOpts)
+type forwardCacheClient struct {
+	namespace string
+	context   string
+	init      sync.Once
+	client    cache.CacheClient
+	err       error
+}
+
+func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error {
+	c.init.Do(func() {
+		overrides := clientcmd.ConfigOverrides{
+			CurrentContext: c.context,
+		}
+		redisPort, err := kubeutil.PortForward(6379, c.namespace, &overrides,
+			"app.kubernetes.io/name=argocd-redis-ha-haproxy", "app.kubernetes.io/name=argocd-redis")
+		if err != nil {
+			c.err = err
+			return
+		}
+
+		redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)})
+		c.client = cache.NewRedisCache(redisClient, time.Hour)
+	})
+	if c.err != nil {
+		return c.err
+	}
+	return action(c.client)
+}
+
+func (c *forwardCacheClient) Set(item *cache.Item) error {
+	return c.doLazy(func(client cache.CacheClient) error {
+		return client.Set(item)
+	})
+}
+
+func (c *forwardCacheClient) Get(key string, obj interface{}) error {
+	return c.doLazy(func(client cache.CacheClient) error {
+		return client.Get(key, obj)
+	})
+}
+
+func (c *forwardCacheClient) Delete(key string) error {
+	return c.doLazy(func(client cache.CacheClient) error {
+		return client.Delete(key)
+	})
+}
+
+func (c *forwardCacheClient) OnUpdated(ctx context.Context, key string, callback func() error) error {
+	return c.doLazy(func(client cache.CacheClient) error {
+		return client.OnUpdated(ctx, key, callback)
+	})
+}
+
+func (c *forwardCacheClient) NotifyUpdated(key string) error {
+	return c.doLazy(func(client cache.CacheClient) error {
+		return client.NotifyUpdated(key)
+	})
+}
+
+type forwardRepoClientset struct {
+	namespace     string
+	context       string
+	init          sync.Once
+	repoClientset repoapiclient.Clientset
+	err           error
+}
+
+func (c *forwardRepoClientset) NewRepoServerClient() (io.Closer, repoapiclient.RepoServerServiceClient, error) {
+	c.init.Do(func() {
+		overrides := clientcmd.ConfigOverrides{
+			CurrentContext: c.context,
+		}
+		repoServerPort, err := kubeutil.PortForward(8081, c.namespace, &overrides, "app.kubernetes.io/name=argocd-repo-server")
+		if err != nil {
+			c.err = err
+			return
+		}
+		c.repoClientset = repoapiclient.NewRepoServerClientset(fmt.Sprintf("localhost:%d", repoServerPort), 60, repoapiclient.TLSConfiguration{
+			DisableTLS: false, StrictValidation: false})
+	})
+	if c.err != nil {
+		return nil, nil, c.err
+	}
+	return c.repoClientset.NewRepoServerClient()
+}
+
+func testAPI(clientOpts *apiclient.ClientOptions) error {
+	apiClient, err := apiclient.NewClient(clientOpts)
 	if err != nil {
 		return err
 	}
@@ -46,122 +137,112 @@ func testAPI(clientOpts *argoapi.ClientOptions) error {
 	return err
 }
 
-func retrieveContextIfChanged(contextFlag *flag.Flag) string {
-	if contextFlag != nil && contextFlag.Changed {
-		return contextFlag.Value.String()
-	}
-	return ""
-}
-
-// InitCommand allows executing command in a headless mode: on the fly starts Argo CD API server and
+// StartLocalServer allows executing command in a headless mode: on the fly starts Argo CD API server and
 // changes provided client options to use started API server port
-func InitCommand(cmd *cobra.Command, clientOpts *argoapi.ClientOptions, port *int, address *string) *cobra.Command {
-	ctx, cancel := context.WithCancel(context.Background())
+func StartLocalServer(clientOpts *apiclient.ClientOptions, ctxStr string, port *int, address *string) error {
 	flags := pflag.NewFlagSet("tmp", pflag.ContinueOnError)
 	clientConfig := cli.AddKubectlFlagsToSet(flags)
-	// copy k8s persistent flags into argocd command flags
-	flags.VisitAll(func(flag *pflag.Flag) {
-		// skip Kubernetes server flags since argocd has it's own server flag
-		if flag.Name == "server" {
-			return
-		}
-		cmd.Flags().AddFlag(flag)
-	})
-	cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
-		startInProcessAPI := clientOpts.Core
-		if !startInProcessAPI {
-			localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
-			if err != nil {
-				return err
-			}
-			if localCfg != nil {
-				configCtx, err := localCfg.ResolveContext(clientOpts.Context)
-				if err != nil {
-					return err
-				}
-				startInProcessAPI = configCtx.Server.Core
-			}
-		}
-		if !startInProcessAPI {
-			return nil
-		}
-
-		// get rid of logging error handler
-		runtime.ErrorHandlers = runtime.ErrorHandlers[1:]
-		cli.SetLogLevel(log.ErrorLevel.String())
-		log.SetLevel(log.ErrorLevel)
-		os.Setenv(v1alpha1.EnvVarFakeInClusterConfig, "true")
-		if address == nil {
-			address = pointer.String("localhost")
+	startInProcessAPI := clientOpts.Core
+	if !startInProcessAPI {
+		localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
+		if err != nil {
+			return err
 		}
-		if port == nil || *port == 0 {
-			addr := fmt.Sprintf("%s:0", *address)
-			ln, err := net.Listen("tcp", addr)
+		if localCfg != nil {
+			configCtx, err := localCfg.ResolveContext(clientOpts.Context)
 			if err != nil {
 				return err
 			}
-			port = &ln.Addr().(*net.TCPAddr).Port
-			io.Close(ln)
+			startInProcessAPI = configCtx.Server.Core
 		}
+	}
+	if !startInProcessAPI {
+		return nil
+	}
 
-		restConfig, err := clientConfig.ClientConfig()
-		if err != nil {
-			return err
-		}
-		appClientset, err := appclientset.NewForConfig(restConfig)
-		if err != nil {
-			return err
-		}
-		kubeClientset, err := kubernetes.NewForConfig(restConfig)
+	// get rid of logging error handler
+	runtime.ErrorHandlers = runtime.ErrorHandlers[1:]
+	cli.SetLogLevel(log.ErrorLevel.String())
+	log.SetLevel(log.ErrorLevel)
+	os.Setenv(v1alpha1.EnvVarFakeInClusterConfig, "true")
+	if address == nil {
+		address = pointer.String("localhost")
+	}
+	if port == nil || *port == 0 {
+		addr := fmt.Sprintf("%s:0", *address)
+		ln, err := net.Listen("tcp", addr)
 		if err != nil {
 			return err
 		}
+		port = &ln.Addr().(*net.TCPAddr).Port
+		io.Close(ln)
+	}
 
-		namespace, _, err := clientConfig.Namespace()
-		if err != nil {
-			return err
-		}
+	restConfig, err := clientConfig.ClientConfig()
+	if err != nil {
+		return err
+	}
+	appClientset, err := appclientset.NewForConfig(restConfig)
+	if err != nil {
+		return err
+	}
+	kubeClientset, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		return err
+	}
 
-		context := retrieveContextIfChanged(cmd.Flag("context"))
+	namespace, _, err := clientConfig.Namespace()
+	if err != nil {
+		return err
+	}
 
-		mr, err := miniredis.Run()
-		if err != nil {
-			return err
-		}
+	mr, err := miniredis.Run()
+	if err != nil {
+		return err
+	}
+	ctx := context.Background()
+	appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr}), time.Hour)
+	srv := server.NewServer(ctx, server.ArgoCDServerOpts{
+		EnableGZip:    false,
+		Namespace:     namespace,
+		ListenPort:    *port,
+		AppClientset:  appClientset,
+		DisableAuth:   true,
+		RedisClient:   redis.NewClient(&redis.Options{Addr: mr.Addr()}),
+		Cache:         servercache.NewCache(appstateCache, 0, 0, 0),
+		KubeClientset: kubeClientset,
+		Insecure:      true,
+		ListenHost:    *address,
+		RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr},
+	})
 
-		appstateCache := appstatecache.NewCache(cacheutil.NewCache(&forwardCacheClient{namespace: namespace, context: context}), time.Hour)
-		srv := server.NewServer(ctx, server.ArgoCDServerOpts{
-			EnableGZip:    false,
-			Namespace:     namespace,
-			ListenPort:    *port,
-			AppClientset:  appClientset,
-			DisableAuth:   true,
-			RedisClient:   redis.NewClient(&redis.Options{Addr: mr.Addr()}),
-			Cache:         servercache.NewCache(appstateCache, 0, 0, 0),
-			KubeClientset: kubeClientset,
-			Insecure:      true,
-			ListenHost:    *address,
-			RepoClientset: &forwardRepoClientset{namespace: namespace, context: context},
-		})
-
-		go srv.Run(ctx, *port, 0)
-		clientOpts.ServerAddr = fmt.Sprintf("%s:%d", *address, *port)
-		clientOpts.PlainText = true
-		if !cache.WaitForCacheSync(ctx.Done(), srv.Initialized) {
-			log.Fatal("Timed out waiting for project cache to sync")
-		}
-		tries := 5
-		for i := 0; i < tries; i++ {
-			err = testAPI(clientOpts)
-			if err == nil {
-				break
-			}
-			time.Sleep(time.Second)
+	go srv.Run(ctx, *port, 0)
+	clientOpts.ServerAddr = fmt.Sprintf("%s:%d", *address, *port)
+	clientOpts.PlainText = true
+	if !cache2.WaitForCacheSync(ctx.Done(), srv.Initialized) {
+		log.Fatal("Timed out waiting for project cache to sync")
+	}
+	tries := 5
+	for i := 0; i < tries; i++ {
+		err = testAPI(clientOpts)
+		if err == nil {
+			break
 		}
-		return err
+		time.Sleep(time.Second)
 	}
-	cmd.PostRun = func(cmd *cobra.Command, args []string) {
-		cancel()
+	return err
+}
+
+// NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
+func NewClientOrDie(opts *apiclient.ClientOptions, c *cobra.Command) apiclient.Client {
+	ctxStr := initialize.RetrieveContextIfChanged(c.Flag("context"))
+	err := StartLocalServer(opts, ctxStr, nil, nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+	client, err := apiclient.NewClient(opts)
+	if err != nil {
+		log.Fatal(err)
 	}
-	return cmd
+	return client
 }
diff --git a/cmd/argocd/commands/initialize/cmd.go b/cmd/argocd/commands/initialize/cmd.go
new file mode 100644
index 000000000..76a6470f0
--- /dev/null
+++ b/cmd/argocd/commands/initialize/cmd.go
@@ -0,0 +1,32 @@
+package initialize
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+	flag "github.com/spf13/pflag"
+
+	"github.com/argoproj/argo-cd/v2/util/cli"
+)
+
+func RetrieveContextIfChanged(contextFlag *flag.Flag) string {
+	if contextFlag != nil && contextFlag.Changed {
+		return contextFlag.Value.String()
+	}
+	return ""
+}
+
+// InitCommand allows executing command in a headless mode: on the fly starts Argo CD API server and
+// changes provided client options to use started API server port
+func InitCommand(cmd *cobra.Command) *cobra.Command {
+	flags := pflag.NewFlagSet("tmp", pflag.ContinueOnError)
+	cli.AddKubectlFlagsToSet(flags)
+	// copy k8s persistent flags into argocd command flags
+	flags.VisitAll(func(flag *pflag.Flag) {
+		// skip Kubernetes server flags since argocd has it's own server flag
+		if flag.Name == "server" {
+			return
+		}
+		cmd.Flags().AddFlag(flag)
+	})
+	return cmd
+}
diff --git a/cmd/argocd/commands/login.go b/cmd/argocd/commands/login.go
index 037985a85..9abc658ca 100644
--- a/cmd/argocd/commands/login.go
+++ b/cmd/argocd/commands/login.go
@@ -19,6 +19,7 @@ import (
 	"github.com/spf13/cobra"
 	"golang.org/x/oauth2"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	sessionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
 	settingspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings"
@@ -111,7 +112,7 @@ argocd login cd.argoproj.io --core`,
 			var tokenString string
 			var refreshToken string
 			if !globalClientOpts.Core {
-				acdClient := argocdclient.NewClientOrDie(&clientOpts)
+				acdClient := headless.NewClientOrDie(&clientOpts, c)
 				setConn, setIf := acdClient.NewSettingsClientOrDie()
 				defer io.Close(setConn)
 				if !sso {
diff --git a/cmd/argocd/commands/project.go b/cmd/argocd/commands/project.go
index 19d3aecd2..3a9470abd 100644
--- a/cmd/argocd/commands/project.go
+++ b/cmd/argocd/commands/project.go
@@ -17,6 +17,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
@@ -92,7 +93,7 @@ func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
 			proj, err := cmdutil.ConstructAppProj(fileURL, args, opts, c)
 			errors.CheckError(err)
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 			_, err = projIf.Create(context.Background(), &projectpkg.ProjectCreateRequest{Project: proj, Upsert: upsert})
 			errors.CheckError(err)
@@ -122,7 +123,7 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 				os.Exit(1)
 			}
 			projName := args[0]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -159,7 +160,7 @@ func NewProjectAddSignatureKeyCommand(clientOpts *argocdclient.ClientOptions) *c
 				log.Fatalf("%s is not a valid GnuPG key ID", signatureKey)
 			}
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -191,7 +192,7 @@ func NewProjectRemoveSignatureKeyCommand(clientOpts *argocdclient.ClientOptions)
 			projName := args[0]
 			signatureKey := args[1]
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -239,7 +240,7 @@ func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *co
 			projName := args[0]
 			namespace := args[2]
 			destination := buildApplicationDestination(args[1], namespace, nameInsteadServer)
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -274,7 +275,7 @@ func NewProjectRemoveDestinationCommand(clientOpts *argocdclient.ClientOptions)
 			projName := args[0]
 			server := args[1]
 			namespace := args[2]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -316,7 +317,7 @@ func NewProjectAddOrphanedIgnoreCommand(clientOpts *argocdclient.ClientOptions)
 			projName := args[0]
 			group := args[1]
 			kind := args[2]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -359,7 +360,7 @@ func NewProjectRemoveOrphanedIgnoreCommand(clientOpts *argocdclient.ClientOption
 			projName := args[0]
 			group := args[1]
 			kind := args[2]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -402,7 +403,7 @@ func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
 			}
 			projName := args[0]
 			url := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -474,7 +475,7 @@ func modifyResourceListCmd(cmdUse, cmdDesc string, clientOpts *argocdclient.Clie
 				os.Exit(1)
 			}
 			projName, group, kind := args[0], args[1], args[2]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -550,7 +551,7 @@ func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobr
 			}
 			projName := args[0]
 			url := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -586,7 +587,7 @@ func NewProjectDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 			for _, name := range args {
 				_, err := projIf.Delete(context.Background(), &projectpkg.ProjectQuery{Name: name})
@@ -623,7 +624,7 @@ func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
 		Use:   "list",
 		Short: "List projects",
 		Run: func(c *cobra.Command, args []string) {
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 			projects, err := projIf.List(context.Background(), &projectpkg.ProjectQuery{})
 			errors.CheckError(err)
@@ -771,7 +772,7 @@ func NewProjectGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 				os.Exit(1)
 			}
 			projName := args[0]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 			p, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
 			errors.CheckError(err)
@@ -801,7 +802,7 @@ func NewProjectEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
 				os.Exit(1)
 			}
 			projName := args[0]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer argoio.Close(conn)
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
 			errors.CheckError(err)
diff --git a/cmd/argocd/commands/project_role.go b/cmd/argocd/commands/project_role.go
index f7dddb6ca..2cb47921a 100644
--- a/cmd/argocd/commands/project_role.go
+++ b/cmd/argocd/commands/project_role.go
@@ -12,6 +12,7 @@ import (
 	jwtgo "github.com/golang-jwt/jwt/v4"
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -63,7 +64,7 @@ func NewProjectRoleAddPolicyCommand(clientOpts *argocdclient.ClientOptions) *cob
 			}
 			projName := args[0]
 			roleName := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -98,7 +99,7 @@ func NewProjectRoleRemovePolicyCommand(clientOpts *argocdclient.ClientOptions) *
 			}
 			projName := args[0]
 			roleName := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -143,7 +144,7 @@ func NewProjectRoleCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
 			}
 			projName := args[0]
 			roleName := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -177,7 +178,7 @@ func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
 			}
 			projName := args[0]
 			roleName := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -225,7 +226,7 @@ func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *c
 			}
 			projName := args[0]
 			roleName := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 			if expiresIn == "" {
 				expiresIn = "0s"
@@ -289,7 +290,7 @@ func NewProjectRoleListTokensCommand(clientOpts *argocdclient.ClientOptions) *co
 			projName := args[0]
 			roleName := args[1]
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -340,7 +341,7 @@ func NewProjectRoleDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *c
 			issuedAt, err := strconv.ParseInt(args[2], 10, 64)
 			errors.CheckError(err)
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			_, err = projIf.DeleteToken(context.Background(), &projectpkg.ProjectTokenDeleteRequest{Project: projName, Role: roleName, Iat: issuedAt})
@@ -381,7 +382,7 @@ func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				os.Exit(1)
 			}
 			projName := args[0]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			project, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -415,7 +416,7 @@ func NewProjectRoleGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
 			}
 			projName := args[0]
 			roleName := args[1]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -457,7 +458,7 @@ func NewProjectRoleAddGroupCommand(clientOpts *argocdclient.ClientOptions) *cobr
 				os.Exit(1)
 			}
 			projName, roleName, groupName := args[0], args[1], args[2]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
 			errors.CheckError(err)
@@ -486,7 +487,7 @@ func NewProjectRoleRemoveGroupCommand(clientOpts *argocdclient.ClientOptions) *c
 				os.Exit(1)
 			}
 			projName, roleName, groupName := args[0], args[1], args[2]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
 			errors.CheckError(err)
diff --git a/cmd/argocd/commands/projectwindows.go b/cmd/argocd/commands/projectwindows.go
index 6f27e7327..a7c6e555b 100644
--- a/cmd/argocd/commands/projectwindows.go
+++ b/cmd/argocd/commands/projectwindows.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -36,7 +37,7 @@ func NewProjectWindowsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
 	return roleCommand
 }
 
-// NewProjectSyncWindowsDisableManualSyncCommand returns a new instance of an `argocd proj windows disable-manual-sync` command
+// NewProjectWindowsDisableManualSyncCommand returns a new instance of an `argocd proj windows disable-manual-sync` command
 func NewProjectWindowsDisableManualSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "disable-manual-sync PROJECT ID",
@@ -52,7 +53,7 @@ func NewProjectWindowsDisableManualSyncCommand(clientOpts *argocdclient.ClientOp
 			id, err := strconv.Atoi(args[1])
 			errors.CheckError(err)
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -87,7 +88,7 @@ func NewProjectWindowsEnableManualSyncCommand(clientOpts *argocdclient.ClientOpt
 			id, err := strconv.Atoi(args[1])
 			errors.CheckError(err)
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -127,7 +128,7 @@ func NewProjectWindowsAddWindowCommand(clientOpts *argocdclient.ClientOptions) *
 				os.Exit(1)
 			}
 			projName := args[0]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -167,7 +168,7 @@ func NewProjectWindowsDeleteCommand(clientOpts *argocdclient.ClientOptions) *cob
 			id, err := strconv.Atoi(args[1])
 			errors.CheckError(err)
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -207,7 +208,7 @@ func NewProjectWindowsUpdateCommand(clientOpts *argocdclient.ClientOptions) *cob
 			id, err := strconv.Atoi(args[1])
 			errors.CheckError(err)
 
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
@@ -249,7 +250,7 @@ func NewProjectWindowsListCommand(clientOpts *argocdclient.ClientOptions) *cobra
 				os.Exit(1)
 			}
 			projName := args[0]
-			conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
+			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
 			defer io.Close(conn)
 
 			proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
diff --git a/cmd/argocd/commands/relogin.go b/cmd/argocd/commands/relogin.go
index 26e7ded95..69cfa4aa0 100644
--- a/cmd/argocd/commands/relogin.go
+++ b/cmd/argocd/commands/relogin.go
@@ -9,6 +9,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	settingspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings"
 	"github.com/argoproj/argo-cd/v2/util/errors"
@@ -53,7 +54,7 @@ func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comm
 				PlainText:         configCtx.Server.PlainText,
 				Headers:           globalClientOpts.Headers,
 			}
-			acdClient := argocdclient.NewClientOrDie(&clientOpts)
+			acdClient := headless.NewClientOrDie(&clientOpts, c)
 			claims, err := configCtx.User.Claims()
 			errors.CheckError(err)
 			if claims.Issuer == session.SessionManagerClaimsIssuer {
diff --git a/cmd/argocd/commands/repo.go b/cmd/argocd/commands/repo.go
index 246b01af0..96a845da9 100644
--- a/cmd/argocd/commands/repo.go
+++ b/cmd/argocd/commands/repo.go
@@ -10,6 +10,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
@@ -150,7 +151,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 				errors.CheckError(fmt.Errorf("Must specify --name for repos of type 'helm'"))
 			}
 
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
 			defer io.Close(conn)
 
 			// If the user set a username, but didn't supply password via --password,
@@ -212,7 +213,7 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
 			defer io.Close(conn)
 			for _, repoURL := range args {
 				_, err := repoIf.Delete(context.Background(), &repositorypkg.RepoQuery{Repo: repoURL})
@@ -261,7 +262,7 @@ func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 		Use:   "list",
 		Short: "List configured repositories",
 		Run: func(c *cobra.Command, args []string) {
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
 			defer io.Close(conn)
 			forceRefresh := false
 			switch refresh {
@@ -310,7 +311,7 @@ func NewRepoGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 
 			// Repository URL
 			repoURL := args[0]
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
 			defer io.Close(conn)
 			forceRefresh := false
 			switch refresh {
diff --git a/cmd/argocd/commands/repocreds.go b/cmd/argocd/commands/repocreds.go
index d81862f81..0386047ed 100644
--- a/cmd/argocd/commands/repocreds.go
+++ b/cmd/argocd/commands/repocreds.go
@@ -10,6 +10,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	"github.com/argoproj/argo-cd/v2/common"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	repocredspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repocreds"
@@ -126,7 +127,7 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
 				}
 			}
 
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoCredsClientOrDie()
 			defer io.Close(conn)
 
 			// If the user set a username, but didn't supply password via --password,
@@ -170,7 +171,7 @@ func NewRepoCredsRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
 				c.HelpFunc()(c, args)
 				os.Exit(1)
 			}
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoCredsClientOrDie()
 			defer io.Close(conn)
 			for _, repoURL := range args {
 				_, err := repoIf.DeleteRepositoryCredentials(context.Background(), &repocredspkg.RepoCredsDeleteRequest{Url: repoURL})
@@ -211,7 +212,7 @@ func NewRepoCredsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
 		Use:   "list",
 		Short: "List configured repository credentials",
 		Run: func(c *cobra.Command, args []string) {
-			conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
+			conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoCredsClientOrDie()
 			defer io.Close(conn)
 			repos, err := repoIf.ListRepositoryCredentials(context.Background(), &repocredspkg.RepoCredsQuery{})
 			errors.CheckError(err)
diff --git a/cmd/argocd/commands/root.go b/cmd/argocd/commands/root.go
index 7ec9d022c..0c708e616 100644
--- a/cmd/argocd/commands/root.go
+++ b/cmd/argocd/commands/root.go
@@ -5,7 +5,7 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 
 	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/admin"
-	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
 	cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	"github.com/argoproj/argo-cd/v2/util/cli"
@@ -40,19 +40,19 @@ func NewCommand() *cobra.Command {
 	}
 
 	command.AddCommand(NewCompletionCommand())
-	command.AddCommand(headless.InitCommand(NewVersionCmd(&clientOpts), &clientOpts, nil, nil))
-	command.AddCommand(headless.InitCommand(NewClusterCommand(&clientOpts, pathOpts), &clientOpts, nil, nil))
-	command.AddCommand(headless.InitCommand(NewApplicationCommand(&clientOpts), &clientOpts, nil, nil))
+	command.AddCommand(initialize.InitCommand(NewVersionCmd(&clientOpts)))
+	command.AddCommand(initialize.InitCommand(NewClusterCommand(&clientOpts, pathOpts)))
+	command.AddCommand(initialize.InitCommand(NewApplicationCommand(&clientOpts)))
 	command.AddCommand(NewLoginCommand(&clientOpts))
 	command.AddCommand(NewReloginCommand(&clientOpts))
-	command.AddCommand(headless.InitCommand(NewRepoCommand(&clientOpts), &clientOpts, nil, nil))
-	command.AddCommand(headless.InitCommand(NewRepoCredsCommand(&clientOpts), &clientOpts, nil, nil))
+	command.AddCommand(initialize.InitCommand(NewRepoCommand(&clientOpts)))
+	command.AddCommand(initialize.InitCommand(NewRepoCredsCommand(&clientOpts)))
 	command.AddCommand(NewContextCommand(&clientOpts))
-	command.AddCommand(headless.InitCommand(NewProjectCommand(&clientOpts), &clientOpts, nil, nil))
-	command.AddCommand(headless.InitCommand(NewAccountCommand(&clientOpts), &clientOpts, nil, nil))
+	command.AddCommand(initialize.InitCommand(NewProjectCommand(&clientOpts)))
+	command.AddCommand(initialize.InitCommand(NewAccountCommand(&clientOpts)))
 	command.AddCommand(NewLogoutCommand(&clientOpts))
-	command.AddCommand(headless.InitCommand(NewCertCommand(&clientOpts), &clientOpts, nil, nil))
-	command.AddCommand(headless.InitCommand(NewGPGCommand(&clientOpts), &clientOpts, nil, nil))
+	command.AddCommand(initialize.InitCommand(NewCertCommand(&clientOpts)))
+	command.AddCommand(initialize.InitCommand(NewGPGCommand(&clientOpts)))
 	command.AddCommand(admin.NewAdminCommand())
 
 	defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath()
diff --git a/cmd/argocd/commands/version.go b/cmd/argocd/commands/version.go
index 7efbd20e3..a1f8f5a12 100644
--- a/cmd/argocd/commands/version.go
+++ b/cmd/argocd/commands/version.go
@@ -8,6 +8,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
 	"github.com/argoproj/argo-cd/v2/common"
 	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	"github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
@@ -40,7 +41,6 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 `,
 		Run: func(cmd *cobra.Command, args []string) {
 			cv := common.GetVersion()
-
 			switch output {
 			case "yaml", "json":
 				v := make(map[string]interface{})
@@ -52,7 +52,7 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 				}
 
 				if !client {
-					sv := getServerVersion(clientOpts)
+					sv := getServerVersion(clientOpts, cmd)
 
 					if short {
 						v["server"] = map[string]string{"argocd-server": sv.Version}
@@ -67,7 +67,7 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 				printClientVersion(&cv, short || (output == "short"))
 
 				if !client {
-					sv := getServerVersion(clientOpts)
+					sv := getServerVersion(clientOpts, cmd)
 					printServerVersion(sv, short || (output == "short"))
 				}
 			default:
@@ -81,8 +81,8 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
 	return &versionCmd
 }
 
-func getServerVersion(options *argocdclient.ClientOptions) *version.VersionMessage {
-	conn, versionIf := argocdclient.NewClientOrDie(options).NewVersionClientOrDie()
+func getServerVersion(options *argocdclient.ClientOptions, c *cobra.Command) *version.VersionMessage {
+	conn, versionIf := headless.NewClientOrDie(options, c).NewVersionClientOrDie()
 	defer argoio.Close(conn)
 
 	v, err := versionIf.Version(context.Background(), &empty.Empty{})
diff --git a/controller/cache/cache.go b/controller/cache/cache.go
index a3486f909..f22160bf7 100644
--- a/controller/cache/cache.go
+++ b/controller/cache/cache.go
@@ -2,10 +2,12 @@ package cache
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"math"
 	"reflect"
 	"sync"
+	"syscall"
 	"time"
 
 	clustercache "github.com/argoproj/gitops-engine/pkg/cache"
@@ -14,6 +16,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/sync/semaphore"
 	v1 "k8s.io/api/core/v1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -44,6 +47,12 @@ const (
 	// This is used to limit the number of concurrent memory consuming operations on the
 	// k8s list queries results across all clusters to avoid memory spikes during cache initialization.
 	EnvClusterCacheListSemaphore = "ARGOCD_CLUSTER_CACHE_LIST_SEMAPHORE"
+
+	// EnvClusterCacheRetryLimit is the env variable to control the retry limit for listing resources during cluster cache sync
+	EnvClusterCacheAttemptLimit = "ARGOCD_CLUSTER_CACHE_ATTEMPT_LIMIT"
+
+	// EnvClusterCacheRetryUseBackoff is the env variable to control whether to use a backoff strategy with the retry during cluster cache sync
+	EnvClusterCacheRetryUseBackoff = "ARGOCD_CLUSTER_CACHE_RETRY_USE_BACKOFF"
 )
 
 // GitOps engine cluster cache tuning options
@@ -62,6 +71,13 @@ var (
 	// clusterCacheListPageSize is the page size when performing K8s list requests.
 	// 500 is equal to kubectl's size
 	clusterCacheListPageSize int64 = 500
+
+	// clusterCacheRetryLimit sets a retry limit for failed requests during cluster cache sync
+	// If set to 1, retries are disabled.
+	clusterCacheAttemptLimit int32 = 1
+
+	// clusterCacheRetryUseBackoff specifies whether to use a backoff strategy on cluster cache sync, if retry is enabled
+	clusterCacheRetryUseBackoff bool = false
 )
 
 func init() {
@@ -69,6 +85,8 @@ func init() {
 	clusterCacheWatchResyncDuration = env.ParseDurationFromEnv(EnvClusterCacheWatchResyncDuration, clusterCacheWatchResyncDuration, 0, math.MaxInt64)
 	clusterCacheListPageSize = env.ParseInt64FromEnv(EnvClusterCacheListPageSize, clusterCacheListPageSize, 0, math.MaxInt64)
 	clusterCacheListSemaphoreSize = env.ParseInt64FromEnv(EnvClusterCacheListSemaphore, clusterCacheListSemaphoreSize, 0, math.MaxInt64)
+	clusterCacheAttemptLimit = int32(env.ParseInt64FromEnv(EnvClusterCacheAttemptLimit, 1, 1, math.MaxInt32))
+	clusterCacheRetryUseBackoff = env.ParseBoolFromEnv(EnvClusterCacheRetryUseBackoff, false)
 }
 
 type LiveStateCache interface {
@@ -278,6 +296,19 @@ func skipAppRequeuing(key kube.ResourceKey) bool {
 	return ignoredRefreshResources[key.Group+"/"+key.Kind]
 }
 
+// isRetryableError is a helper method to see whether an error
+// returned from the dynamic client is potentially retryable.
+func isRetryableError(err error) bool {
+	return kerrors.IsInternalError(err) ||
+		kerrors.IsInvalid(err) ||
+		kerrors.IsServerTimeout(err) ||
+		kerrors.IsServiceUnavailable(err) ||
+		kerrors.IsTimeout(err) ||
+		kerrors.IsUnexpectedObjectError(err) ||
+		kerrors.IsUnexpectedServerError(err) ||
+		errors.Is(err, syscall.ECONNRESET)
+}
+
 func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, error) {
 	c.lock.RLock()
 	clusterCache, ok := c.clusters[server]
@@ -330,6 +361,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
 			return res, res.AppName != "" || gvk.Kind == kube.CustomResourceDefinitionKind
 		}),
 		clustercache.SetLogr(logutils.NewLogrusLogger(log.WithField("server", cluster.Server))),
+		clustercache.SetRetryOptions(clusterCacheAttemptLimit, clusterCacheRetryUseBackoff, isRetryableError),
 	}
 
 	clusterCache = clustercache.NewClusterCache(cluster.RESTConfig(), clusterCacheOpts...)
diff --git a/controller/clusterinfoupdater.go b/controller/clusterinfoupdater.go
index 139dfc2bc..b11a37318 100644
--- a/controller/clusterinfoupdater.go
+++ b/controller/clusterinfoupdater.go
@@ -64,6 +64,7 @@ func (c *clusterInfoUpdater) updateClusters() {
 	clusters, err := c.db.ListClusters(context.Background())
 	if err != nil {
 		log.Warnf("Failed to save clusters info: %v", err)
+		return
 	}
 	var clustersFiltered []appv1.Cluster
 	if c.clusterFilter == nil {
diff --git a/docs/operator-manual/application.yaml b/docs/operator-manual/application.yaml
index 4da754b05..d2b8bd006 100644
--- a/docs/operator-manual/application.yaml
+++ b/docs/operator-manual/application.yaml
@@ -103,6 +103,7 @@ spec:
   # Destination cluster and namespace to deploy the application
   destination:
     server: https://kubernetes.default.svc
+    # The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace
     namespace: guestbook
 
   # Sync policy
diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml
index b6260b8d6..c66c64792 100644
--- a/docs/operator-manual/argocd-cm.yaml
+++ b/docs/operator-manual/argocd-cm.yaml
@@ -13,6 +13,12 @@ data:
   # Enables application status badge feature
   statusbadge.enabled: "true"
 
+  # Override the Argo CD hostname root URL for both the project and the application status badges.
+  # Here is an example of the application status badge for the app `myapp` to see what is replaced.
+  #    <statusbadge.url>api/badge?name=myapp&revision=true
+  # Provide custom URL to override. You must include the trailing forward slash:
+  statusbadge.url: "https://cd-status.apps.argoproj.io/"
+
   # Enables anonymous user access. The anonymous users get default role permissions specified argocd-rbac-cm.yaml.
   users.anonymous.enabled: "true"
   # Specifies token expiration duration
@@ -254,3 +260,6 @@ data:
   # published to the repository. Reconciliation by timeout is disabled if timeout is set to 0. Three minutes by default.
   # > Note: argocd-repo-server deployment must be manually restarted after changing the setting.
   timeout.reconciliation: 180s
+
+  # cluster.inClusterEnabled indicates whether to allow in-cluster server address. This is enabled by default.
+  cluster.inClusterEnabled: "true"
diff --git a/docs/operator-manual/cluster-bootstrapping.md b/docs/operator-manual/cluster-bootstrapping.md
index 6eb3cdafb..d7f7138d6 100644
--- a/docs/operator-manual/cluster-bootstrapping.md
+++ b/docs/operator-manual/cluster-bootstrapping.md
@@ -98,7 +98,7 @@ View [the example on GitHub](https://github.com/argoproj/argocd-example-apps/tre
 
 ### Cascading deletion
 
-If you want to ensure that child-apps and all of their resources are deleted when the parent-app is deleted make sure to add the appropriate [finalizer](https://argo-cd-docs.readthedocs.io/en/latest/user-guide/app_deletion/#about-the-deletion-finalizer) to your `Application` definition
+If you want to ensure that child-apps and all of their resources are deleted when the parent-app is deleted make sure to add the appropriate [finalizer](../user-guide/app_deletion.md#about-the-deletion-finalizer) to your `Application` definition
 
 ```yaml
 apiVersion: argoproj.io/v1alpha1
diff --git a/docs/operator-manual/high_availability.md b/docs/operator-manual/high_availability.md
index d383171a3..79d36d741 100644
--- a/docs/operator-manual/high_availability.md
+++ b/docs/operator-manual/high_availability.md
@@ -138,6 +138,7 @@ Installations that use a different repo for each app are **not** subject to this
 !!! note
     Application manifest paths annotation support depends on the git provider used for the Application. It is currently only supported for GitHub, GitLab, and Gogs based repos
 I'm using `.Second()` modifier to avoid distracting users who already rely on `--app-resync` flag.
+
 * **Relative path** The annotation might contains relative path. In this case the path is considered relative to the path specified in the application source:
 
 ```yaml
diff --git a/docs/operator-manual/security.md b/docs/operator-manual/security.md
index 04d236ad2..8e0d6b45f 100644
--- a/docs/operator-manual/security.md
+++ b/docs/operator-manual/security.md
@@ -40,6 +40,48 @@ the three components (argocd-server, argocd-repo-server, argocd-application-cont
 API server can enforce the use of TLS 1.2 using the flag: `--tlsminversion 1.2`.
 Communication with Redis is performed over plain HTTP by default. TLS can be setup with command line arguments.
 
+## Git & Helm Repositories
+
+Git and helm repositories are managed by a stand-alone service, called the repo-server. The
+repo-server does not carry any Kubernetes privileges and does not store credentials to any services
+(including git). The repo-server is responsible for cloning repositories which have been permitted
+and trusted by Argo CD operators, and generating kubernetes manifests at a given path in the
+repository. For performance and bandwidth efficiency, the repo-server maintains local clones of
+these repositories so that subsequent commits to the repository are efficiently downloaded.
+
+There are security considerations when configuring git repositories that Argo CD is permitted to
+deploy from. In short, gaining unauthorized write access to a git repository trusted by Argo CD
+will have serious security implications outlined below.
+
+### Unauthorized Deployments
+
+Since Argo CD deploys the Kubernetes resources defined in git, an attacker with access to a trusted
+git repo would be able to affect the Kubernetes resources which are deployed. For example, an
+attacker could update the deployment manifest deploy malicious container images to the environment,
+or delete resources in git causing them to be pruned in the live environment.
+
+### Tool command invocation
+
+In addition to raw YAML, Argo CD natively supports two popular Kubernetes config management tools,
+helm and kustomize. When rendering manifests, Argo CD executes these config management tools
+(i.e. `helm template`, `kustomize build`) to generate the manifests. It is possible that an attacker
+with write access to a trusted git repository may construct malicious helm charts or kustomizations
+that attempt to read files out-of-tree. This includes adjacent git repos, as well as files on the
+repo-server itself. Whether or not this is a risk to your organization depends on if the contents
+in the git repos are sensitive in nature. By default, the repo-server itself does not contain
+sensitive information, but might be configured with Config Management Plugins which do
+(e.g. decryption keys). If such plugins are used, extreme care must be taken to ensure the
+repository contents can be trusted at all times.
+
+### Remote bases and helm chart dependencies
+
+Argo CD's repository allow-list only restricts the initial repository which is cloned. However, both
+kustomize and helm contain features to reference and follow *additional* repositories
+(e.g. kustomize remote bases, helm chart dependencies), of which might not be in the repository
+allow-list. Argo CD operators must understand that users with write access to trusted git
+repositories could reference other remote git repositories containing Kubernetes resources not
+easily searchable or auditable in the configured git repositories.
+
 ## Sensitive Information
 
 ### Secrets
diff --git a/docs/operator-manual/upgrading/2.2-2.3.md b/docs/operator-manual/upgrading/2.2-2.3.md
index 25bb5bdf9..769cd51ab 100644
--- a/docs/operator-manual/upgrading/2.2-2.3.md
+++ b/docs/operator-manual/upgrading/2.2-2.3.md
@@ -33,4 +33,8 @@ data:
 
 ## Upgraded Kustomize Version
 
-Note that bundled Kustomize version has been upgraded from 4.2.0 to 4.4.1.
\ No newline at end of file
+Note that bundled Kustomize version has been upgraded from 4.2.0 to 4.4.1.
+
+## Upgrade Helm Version
+
+Note that bundled Helm version has been upgraded from 3.7.1 to 3.8.0.
\ No newline at end of file
diff --git a/docs/operator-manual/user-management/index.md b/docs/operator-manual/user-management/index.md
index 84107959e..5d43b6b98 100644
--- a/docs/operator-manual/user-management/index.md
+++ b/docs/operator-manual/user-management/index.md
@@ -222,9 +222,10 @@ data:
       - type: OIDC
         id: oidc
         name: OIDC
-        issuer: https://example-OIDC-provider.com
-        clientID: aaaabbbbccccddddeee
-        clientSecret: $dex.oidc.clientSecret
+        config:
+          issuer: https://example-OIDC-provider.com
+          clientID: aaaabbbbccccddddeee
+          clientSecret: $dex.oidc.clientSecret
 ```
 
 ### Requesting additional ID token claims
@@ -243,14 +244,15 @@ data:
       - type: OIDC
         id: oidc
         name: OIDC
-        issuer: https://example-OIDC-provider.com
-        clientID: aaaabbbbccccddddeee
-        clientSecret: $dex.oidc.clientSecret
-        insecureEnableGroups: true
-        scopes:
-        - profile
-        - email
-        - groups
+        config:
+          issuer: https://example-OIDC-provider.com
+          clientID: aaaabbbbccccddddeee
+          clientSecret: $dex.oidc.clientSecret
+          insecureEnableGroups: true
+          scopes:
+          - profile
+          - email
+          - groups
 ```
 
 !!! warning
@@ -272,15 +274,16 @@ data:
       - type: OIDC
         id: oidc
         name: OIDC
-        issuer: https://example-OIDC-provider.com
-        clientID: aaaabbbbccccddddeee
-        clientSecret: $dex.oidc.clientSecret
-        insecureEnableGroups: true
-        scopes:
-        - profile
-        - email
-        - groups
-        getUserInfo: true
+        config:
+          issuer: https://example-OIDC-provider.com
+          clientID: aaaabbbbccccddddeee
+          clientSecret: $dex.oidc.clientSecret
+          insecureEnableGroups: true
+          scopes:
+          - profile
+          - email
+          - groups
+          getUserInfo: true
 ```
 
 ## Existing OIDC Provider
diff --git a/docs/user-guide/application-set.md b/docs/user-guide/application-set.md
index 1a3eb545d..de0a29013 100644
--- a/docs/user-guide/application-set.md
+++ b/docs/user-guide/application-set.md
@@ -42,6 +42,6 @@ Likewise, changes made to the ApplicationSet `template` fields will automaticall
 
 Within ApplicationSet there exist other more powerful generators in addition to the List generator, including the Cluster generator (which automatically uses Argo CD-defined clusters to template Applications), and the Git generator (which uses the files/directories of a Git repository to template applications).
 
-To learn more about the ApplicationSet controller, check out [ApplicationSet documentation](https://argocd-applicationset.readthedocs.io/en/stable/) and [Getting Started](https://argocd-applicationset.readthedocs.io/en/stable/Geting-Started/) to install the ApplicationSet controller alongside Argo CD.
+To learn more about the ApplicationSet controller, check out [ApplicationSet documentation](https://argocd-applicationset.readthedocs.io/en/stable/) and [Getting Started](https://argocd-applicationset.readthedocs.io/en/stable/Getting-Started/) to install the ApplicationSet controller alongside Argo CD.
 
 **Note:** Starting `v2.3` of Argo CD, we don't need to install ApplicationSet Controller separately. It would be instead as part of Argo CD installation.
\ No newline at end of file
diff --git a/docs/user-guide/sync-waves.md b/docs/user-guide/sync-waves.md
index ad6ce8785..932ba396d 100644
--- a/docs/user-guide/sync-waves.md
+++ b/docs/user-guide/sync-waves.md
@@ -38,7 +38,7 @@ When Argo CD starts a sync, it orders the resources in the following precedence:
 
 * The phase
 * The wave they are in (lower values first)
-* By kind (e.g. namespaces first)
+* By kind (e.g. [namespaces first and then other Kubernetes resources, followed by custom resources](https://github.com/argoproj/gitops-engine/blob/bc9ce5764fa306f58cf59199a94f6c968c775a2d/pkg/sync/sync_tasks.go#L27-L66))
 * By name 
 
 It then determines the number of the next wave to apply. This is the first number where any resource is out-of-sync or unhealthy.
@@ -48,3 +48,8 @@ It applies resources in that wave.
 It repeats this process until all phases and waves are in-sync and healthy.
 
 Because an application can have resources that are unhealthy in the first wave, it may be that the app can never get to healthy.
+
+Note that there's currently a delay between each sync wave in order give other controllers a chance to react to the spec change
+that we just applied. This also prevent Argo CD from assessing resource health too quickly (against the stale object), causing
+hooks to fire prematurely. The current delay between each sync wave is 2 seconds and can be configured via environment
+variable `ARGOCD_SYNC_WAVE_DELAY`.
diff --git a/go.mod b/go.mod
index 8b09d8740..d7bf2a26c 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
 	github.com/alicebob/miniredis v2.5.0+incompatible
 	github.com/alicebob/miniredis/v2 v2.14.2
-	github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5
+	github.com/argoproj/gitops-engine v0.5.1-0.20220215202024-c517b47f2f2f
 	github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998
 	github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0
 	github.com/bombsimon/logrusr/v2 v2.0.1
@@ -74,6 +74,7 @@ require (
 	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
 	google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2
 	google.golang.org/grpc v1.40.0
+	google.golang.org/protobuf v1.27.1
 	gopkg.in/go-playground/webhooks.v5 v5.11.0
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/api v0.23.1
@@ -197,7 +198,6 @@ require (
 	gomodules.xyz/envconfig v1.3.1-0.20190308184047-426f31af0d45 // indirect
 	gomodules.xyz/notify v0.1.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
@@ -208,7 +208,7 @@ require (
 	k8s.io/cli-runtime v0.23.1 // indirect
 	k8s.io/component-base v0.23.1 // indirect
 	k8s.io/component-helpers v0.23.1 // indirect
-	k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect
+	k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect
 	k8s.io/kube-aggregator v0.23.1 // indirect
 	k8s.io/kubernetes v1.23.1 // indirect
 	sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
@@ -227,6 +227,7 @@ replace (
 
 	google.golang.org/grpc => google.golang.org/grpc v1.15.0
 
+	// https://github.com/kubernetes/kubernetes/issues/79384#issuecomment-505627280
 	k8s.io/api => k8s.io/api v0.23.1
 	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1
 	k8s.io/apimachinery => k8s.io/apimachinery v0.23.1
diff --git a/go.sum b/go.sum
index b2a7ad7a2..ebd05df59 100644
--- a/go.sum
+++ b/go.sum
@@ -125,8 +125,8 @@ github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc=
-github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5 h1:oMRXPoMzlonjHMUE/dcdimFLiWUTieitanXlCIQf+a8=
-github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5/go.mod h1:UJrK2YMBUbwJZue68mXhSDw+T52egdZWAU1F5cK34ko=
+github.com/argoproj/gitops-engine v0.5.1-0.20220215202024-c517b47f2f2f h1:eO09b/oPk5di9q1HdO68/zdZkjXEhmO4aS4RsWHpDWQ=
+github.com/argoproj/gitops-engine v0.5.1-0.20220215202024-c517b47f2f2f/go.mod h1:UJrK2YMBUbwJZue68mXhSDw+T52egdZWAU1F5cK34ko=
 github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998 h1:V9RDg+IZeebnm3XjkfkbN07VM21Fu1Cy/RJNoHO++VM=
 github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998/go.mod h1:5mKv7zEgI3NO0L+fsuRSwBSY9EIXSuyIsDND8O8TTIw=
 github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc=
@@ -1589,8 +1589,9 @@ k8s.io/controller-manager v0.23.1/go.mod h1:AFE4qIllvTh+nRwGr3SRSUt7F+xVSzXCeb0h
 k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4=
 k8s.io/csi-translation-lib v0.23.1/go.mod h1:0ZyB0cZBV4ZkqibwilEhKnxOne28rq5FDSjO+0MUVio=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c h1:GohjlNKauSai7gN4wsJkeZ3WAJx4Sh+oT/b5IYn5suA=
 k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 h1:TT1WdmqqXareKxZ/oNXEUSwKlLiHzPMyB0t8BaFeBYI=
+k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
 k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
diff --git a/hack/generate-proto.sh b/hack/generate-proto.sh
index a0327ca9c..2253eb0cc 100755
--- a/hack/generate-proto.sh
+++ b/hack/generate-proto.sh
@@ -38,11 +38,21 @@ APIMACHINERY_PKGS=(
 export GO111MODULE=on
 [ -e ./v2 ] || ln -s . v2
 
+# protoc_include is the include directory containing the .proto files distributed with protoc binary
+if [ -d /dist/protoc-include ]; then
+    # containerized codegen build
+    protoc_include=/dist/protoc-include
+else
+    # local codegen build 
+    protoc_include=${PROJECT_ROOT}/dist/protoc-include
+fi
+
 go-to-protobuf \
     --go-header-file=${PROJECT_ROOT}/hack/custom-boilerplate.go.txt \
     --packages=$(IFS=, ; echo "${PACKAGES[*]}") \
     --apimachinery-packages=$(IFS=, ; echo "${APIMACHINERY_PKGS[*]}") \
-    --proto-import=./vendor
+    --proto-import=./vendor \
+    --proto-import=${protoc_include}
 
 # Either protoc-gen-go, protoc-gen-gofast, or protoc-gen-gogofast can be used to build
 # server/*/<service>.pb.go from .proto files. golang/protobuf and gogo/protobuf can be used
@@ -64,7 +74,7 @@ PROTO_FILES=$(find $PROJECT_ROOT \( -name "*.proto" -and -path '*/server/*' -or
 for i in ${PROTO_FILES}; do
     protoc \
         -I${PROJECT_ROOT} \
-        -I/usr/local/include \
+        -I${protoc_include} \
         -I./vendor \
         -I$GOPATH/src \
         -I${GOOGLE_PROTO_API_PATH} \
diff --git a/hack/installers/checksums/protoc_3.17.3_darwin_amd64.zip.sha256 b/hack/installers/checksums/protoc_3.17.3_darwin_amd64.zip.sha256
new file mode 100644
index 000000000..b060cafb1
--- /dev/null
+++ b/hack/installers/checksums/protoc_3.17.3_darwin_amd64.zip.sha256
@@ -0,0 +1,1 @@
+68901eb7ef5b55d7f2df3241ab0b8d97ee5192d3902c59e7adf461adc058e9f1  protoc_3.17.3_darwin_amd64.zip
diff --git a/hack/installers/checksums/protoc_3.17.3_darwin_arm64.zip.sha256 b/hack/installers/checksums/protoc_3.17.3_darwin_arm64.zip.sha256
new file mode 100644
index 000000000..feaa7be14
--- /dev/null
+++ b/hack/installers/checksums/protoc_3.17.3_darwin_arm64.zip.sha256
@@ -0,0 +1,1 @@
+68901eb7ef5b55d7f2df3241ab0b8d97ee5192d3902c59e7adf461adc058e9f1  protoc_3.17.3_darwin_arm64.zip
diff --git a/hack/installers/checksums/protoc_3.17.3_linux_amd64.zip.sha256 b/hack/installers/checksums/protoc_3.17.3_linux_amd64.zip.sha256
new file mode 100644
index 000000000..29274a2ad
--- /dev/null
+++ b/hack/installers/checksums/protoc_3.17.3_linux_amd64.zip.sha256
@@ -0,0 +1,1 @@
+d4246a5136cf9cd1abc851c521a1ad6b8884df4feded8b9cbd5e2a2226d4b357  protoc_3.17.3_linux_amd64.zip
diff --git a/hack/installers/checksums/protoc_3.17.3_linux_arm64.zip.sha256 b/hack/installers/checksums/protoc_3.17.3_linux_arm64.zip.sha256
new file mode 100644
index 000000000..f00d9178e
--- /dev/null
+++ b/hack/installers/checksums/protoc_3.17.3_linux_arm64.zip.sha256
@@ -0,0 +1,1 @@
+ceb29d4890a31ba871829d22c2b7fa28f237d2b91ce4ea2a53e893d60a1cd502  protoc_3.17.3_linux_arm64.zip
\ No newline at end of file
diff --git a/hack/installers/checksums/protoc_3.7.1_linux_amd64.zip.sha256 b/hack/installers/checksums/protoc_3.7.1_linux_amd64.zip.sha256
deleted file mode 100644
index 9542e3902..000000000
--- a/hack/installers/checksums/protoc_3.7.1_linux_amd64.zip.sha256
+++ /dev/null
@@ -1,1 +0,0 @@
-24ea6924faaf94d4a0c5850fdb278290a326eff9a68f36ee5809654faccd0e10  protoc_3.7.1_linux_amd64.zip
diff --git a/hack/installers/checksums/protoc_3.7.1_linux_arm64.zip.sha256 b/hack/installers/checksums/protoc_3.7.1_linux_arm64.zip.sha256
deleted file mode 100644
index 36e8a3364..000000000
--- a/hack/installers/checksums/protoc_3.7.1_linux_arm64.zip.sha256
+++ /dev/null
@@ -1,1 +0,0 @@
-020d82fd48c95b2da0daed250305390927237768523e22f8dd7fac534d8379b9  protoc_3.7.1_linux_arm64.zip
diff --git a/hack/installers/install-codegen-tools.sh b/hack/installers/install-codegen-tools.sh
index 90dc513b9..d908bb7e0 100755
--- a/hack/installers/install-codegen-tools.sh
+++ b/hack/installers/install-codegen-tools.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
 set -eux -o pipefail
 
-KUSTOMIZE_VERSION=4.2.0 "$(dirname $0)/../install.sh" helm2-linux kustomize-linux protoc-linux
+KUSTOMIZE_VERSION=4.2.0 "$(dirname $0)/../install.sh" helm2-linux kustomize-linux protoc
diff --git a/hack/installers/install-protoc-linux.sh b/hack/installers/install-protoc-linux.sh
deleted file mode 100755
index adfb9a11a..000000000
--- a/hack/installers/install-protoc-linux.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-set -eux -o pipefail
-
-. $(dirname $0)/../tool-versions.sh
-
-case $ARCHITECTURE in
-  arm64|arm)
-    export TARGET_FILE=protoc_${protoc_version}_linux_${ARCHITECTURE}.zip
-    [ -e $DOWNLOADS/${TARGET_FILE} ] || curl -sLf --retry 3 -o $DOWNLOADS/${TARGET_FILE} https://github.com/protocolbuffers/protobuf/releases/download/v${protoc_version}/protoc-${protoc_version}-linux-aarch_64.zip
-    $(dirname $0)/compare-chksum.sh
-    mkdir -p /tmp/protoc-${protoc_version}
-    unzip $DOWNLOADS/${TARGET_FILE} -d /tmp/protoc-${protoc_version}
-    sudo install -m 0755 /tmp/protoc-${protoc_version}/bin/protoc /usr/local/bin/protoc
-    sudo cp -a /tmp/protoc-${protoc_version}/include/* /usr/local/include
-    protoc --version
-    ;;
-  *)
-    export TARGET_FILE=protoc_${protoc_version}_linux_${ARCHITECTURE}.zip
-    [ -e $DOWNLOADS/${TARGET_FILE} ] || curl -sLf --retry 3 -o $DOWNLOADS/${TARGET_FILE} https://github.com/protocolbuffers/protobuf/releases/download/v${protoc_version}/protoc-${protoc_version}-linux-x86_64.zip
-    $(dirname $0)/compare-chksum.sh
-    mkdir -p /tmp/protoc-${protoc_version}
-    unzip $DOWNLOADS/${TARGET_FILE} -d /tmp/protoc-${protoc_version}
-    sudo install -m 0755 /tmp/protoc-${protoc_version}/bin/protoc /usr/local/bin/protoc
-    sudo cp -a /tmp/protoc-${protoc_version}/include/* /usr/local/include
-    protoc --version
-    ;;
-esac
\ No newline at end of file
diff --git a/hack/installers/install-protoc.sh b/hack/installers/install-protoc.sh
new file mode 100755
index 000000000..7462d86f0
--- /dev/null
+++ b/hack/installers/install-protoc.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -eux -o pipefail
+
+PROJECT_ROOT=$(cd $(dirname ${BASH_SOURCE})/../..; pwd)
+DIST_PATH="${PROJECT_ROOT}/dist"
+PATH="${DIST_PATH}:${PATH}"
+
+. $(dirname $0)/../tool-versions.sh
+
+OS=$(go env GOOS)
+case $OS in
+  darwin)
+    # For macOS, the x86_64 binary is used even on Apple Silicon (it is run through rosetta), so
+    # we download and install the x86_64 version. See: https://github.com/protocolbuffers/protobuf/pull/8557
+    protoc_os=osx
+    protoc_arch=x86_64
+    ;;
+  *)
+    protoc_os=linux
+    case $ARCHITECTURE in
+      arm64|arm)
+        protoc_arch=aarch_64
+        ;;
+      *)
+        protoc_arch=x86_64
+        ;;
+    esac
+    ;;
+esac
+
+export TARGET_FILE=protoc_${protoc_version}_${OS}_${ARCHITECTURE}.zip
+url=https://github.com/protocolbuffers/protobuf/releases/download/v${protoc_version}/protoc-${protoc_version}-${protoc_os}-${protoc_arch}.zip
+[ -e $DOWNLOADS/${TARGET_FILE} ] || curl -sLf --retry 3 -o $DOWNLOADS/${TARGET_FILE} ${url}
+$(dirname $0)/compare-chksum.sh
+mkdir -p /tmp/protoc-${protoc_version}
+unzip -o $DOWNLOADS/${TARGET_FILE} -d /tmp/protoc-${protoc_version}
+mkdir -p ${DIST_PATH}/protoc-include
+cp /tmp/protoc-${protoc_version}/bin/protoc ${DIST_PATH}/protoc
+chmod +x ${DIST_PATH}/protoc
+cp -a /tmp/protoc-${protoc_version}/include/* ${DIST_PATH}/protoc-include
+chmod -R +rx ${DIST_PATH}/protoc-include
+protoc --version
diff --git a/hack/tool-versions.sh b/hack/tool-versions.sh
index 4d4737825..c73faa69b 100644
--- a/hack/tool-versions.sh
+++ b/hack/tool-versions.sh
@@ -15,4 +15,4 @@ ksonnet_version=0.13.1
 kubectl_version=1.17.8
 kubectx_version=0.6.3
 kustomize4_version=4.4.1
-protoc_version=3.7.1
+protoc_version=3.17.3
diff --git a/pkg/apiclient/settings/settings.pb.go b/pkg/apiclient/settings/settings.pb.go
index 3a13e97a8..ae8f0f9bc 100644
--- a/pkg/apiclient/settings/settings.pb.go
+++ b/pkg/apiclient/settings/settings.pb.go
@@ -96,6 +96,7 @@ type Settings struct {
 	TrackingMethod          string                             `protobuf:"bytes,18,opt,name=trackingMethod,proto3" json:"trackingMethod,omitempty"`
 	UiBannerPermanent       bool                               `protobuf:"varint,19,opt,name=uiBannerPermanent,proto3" json:"uiBannerPermanent,omitempty"`
 	UiBannerPosition        string                             `protobuf:"bytes,20,opt,name=uiBannerPosition,proto3" json:"uiBannerPosition,omitempty"`
+	StatusBadgeRootUrl      string                             `protobuf:"bytes,21,opt,name=statusBadgeRootUrl,proto3" json:"statusBadgeRootUrl,omitempty"`
 	XXX_NoUnkeyedLiteral    struct{}                           `json:"-"`
 	XXX_unrecognized        []byte                             `json:"-"`
 	XXX_sizecache           int32                              `json:"-"`
@@ -274,6 +275,13 @@ func (m *Settings) GetUiBannerPosition() string {
 	return ""
 }
 
+func (m *Settings) GetStatusBadgeRootUrl() string {
+	if m != nil {
+		return m.StatusBadgeRootUrl
+	}
+	return ""
+}
+
 type GoogleAnalyticsConfig struct {
 	TrackingID           string   `protobuf:"bytes,1,opt,name=trackingID,proto3" json:"trackingID,omitempty"`
 	AnonymizeUsers       bool     `protobuf:"varint,2,opt,name=anonymizeUsers,proto3" json:"anonymizeUsers,omitempty"`
@@ -651,75 +659,76 @@ func init() {
 func init() { proto.RegisterFile("server/settings/settings.proto", fileDescriptor_a480d494da040caa) }
 
 var fileDescriptor_a480d494da040caa = []byte{
-	// 1082 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x8f, 0x1b, 0x35,
-	0x14, 0xd7, 0x34, 0xdb, 0xdd, 0xe4, 0xa5, 0xdb, 0xec, 0xba, 0xa5, 0x0c, 0x51, 0xc9, 0x86, 0x1c,
-	0xaa, 0x80, 0x60, 0xc2, 0xa6, 0x42, 0x20, 0xa4, 0x0a, 0x48, 0x52, 0xb5, 0xa1, 0x29, 0x5d, 0xdc,
-	0x6e, 0x0f, 0x48, 0x68, 0xe5, 0x9d, 0x31, 0xb3, 0x26, 0x13, 0x7b, 0x64, 0x7b, 0x42, 0xd3, 0x23,
-	0x37, 0x2e, 0x5c, 0xe0, 0xd3, 0xf0, 0x09, 0x38, 0x22, 0x71, 0x5f, 0xa1, 0x88, 0x8f, 0xc0, 0x07,
-	0x40, 0xf6, 0xfc, 0xd9, 0xd9, 0x24, 0xfc, 0x91, 0x7a, 0x7b, 0xfe, 0xfd, 0xde, 0x3f, 0x3f, 0x3f,
-	0xfb, 0x19, 0x5a, 0x8a, 0xca, 0x39, 0x95, 0x3d, 0x45, 0xb5, 0x66, 0x3c, 0x54, 0x85, 0xe0, 0xc5,
-	0x52, 0x68, 0x81, 0x76, 0xfc, 0x28, 0x51, 0x9a, 0xca, 0xe6, 0xcd, 0x50, 0x84, 0xc2, 0x62, 0x3d,
-	0x23, 0xa5, 0x74, 0xf3, 0x76, 0x28, 0x44, 0x18, 0xd1, 0x1e, 0x89, 0x59, 0x8f, 0x70, 0x2e, 0x34,
-	0xd1, 0x4c, 0xf0, 0xcc, 0xb8, 0x39, 0x09, 0x99, 0x3e, 0x4b, 0x4e, 0x3d, 0x5f, 0xcc, 0x7a, 0x44,
-	0x5a, 0xf3, 0x6f, 0xad, 0xf0, 0x9e, 0x1f, 0xf4, 0xe6, 0xfd, 0x5e, 0x3c, 0x0d, 0x8d, 0xa5, 0xea,
-	0x91, 0x38, 0x8e, 0x98, 0x6f, 0x6d, 0x7b, 0xf3, 0x43, 0x12, 0xc5, 0x67, 0xe4, 0xb0, 0x17, 0x52,
-	0x4e, 0x25, 0xd1, 0x34, 0xc8, 0xbc, 0x7d, 0xfa, 0x1f, 0xde, 0x56, 0x77, 0x22, 0x58, 0xe0, 0xf7,
-	0xfc, 0x88, 0xb0, 0x59, 0x96, 0x4f, 0xa7, 0x01, 0xbb, 0x4f, 0x33, 0xf6, 0xcb, 0x84, 0xca, 0x45,
-	0xe7, 0xaf, 0x1a, 0x54, 0x73, 0x04, 0xbd, 0x01, 0x95, 0x44, 0x46, 0xae, 0xd3, 0x76, 0xba, 0xb5,
-	0xc1, 0xce, 0xf2, 0xfc, 0xa0, 0x72, 0x8c, 0x27, 0xd8, 0x60, 0xe8, 0x7d, 0xa8, 0x05, 0xf4, 0xc5,
-	0x50, 0xf0, 0x6f, 0x58, 0xe8, 0x5e, 0x69, 0x3b, 0xdd, 0x7a, 0x1f, 0x79, 0x59, 0x65, 0xbc, 0x51,
-	0xce, 0xe0, 0x0b, 0x25, 0x34, 0x04, 0x30, 0xf1, 0x33, 0x93, 0x8a, 0x35, 0xb9, 0x51, 0x98, 0x3c,
-	0x19, 0x8f, 0x86, 0x29, 0x35, 0xb8, 0xbe, 0x3c, 0x3f, 0x80, 0x8b, 0x35, 0x2e, 0x99, 0xa1, 0x36,
-	0xd4, 0x49, 0x1c, 0x4f, 0xc8, 0x29, 0x8d, 0x1e, 0xd1, 0x85, 0xbb, 0x65, 0x32, 0xc3, 0x65, 0x08,
-	0x3d, 0x87, 0x7d, 0x49, 0x95, 0x48, 0xa4, 0x4f, 0x9f, 0xcc, 0xa9, 0x94, 0x2c, 0xa0, 0xca, 0xbd,
-	0xda, 0xae, 0x74, 0xeb, 0xfd, 0x6e, 0x11, 0x2d, 0xdf, 0xa1, 0x87, 0x57, 0x55, 0xef, 0x73, 0x2d,
-	0x17, 0x78, 0xdd, 0x05, 0xf2, 0x00, 0x29, 0x4d, 0x74, 0xa2, 0x06, 0x24, 0x08, 0xe9, 0x7d, 0x4e,
-	0x4e, 0x23, 0x1a, 0xb8, 0xdb, 0x6d, 0xa7, 0x5b, 0xc5, 0x1b, 0x18, 0xf4, 0x10, 0x1a, 0x69, 0x27,
-	0x7c, 0xc6, 0x49, 0xb4, 0xd0, 0xcc, 0x57, 0xee, 0x8e, 0xdd, 0x73, 0xab, 0xc8, 0xe2, 0xc1, 0x65,
-	0x3e, 0xdb, 0xee, 0xaa, 0x19, 0x7a, 0x09, 0x7b, 0xd3, 0x44, 0x69, 0x31, 0x63, 0x2f, 0xe9, 0x93,
-	0xd8, 0x76, 0x93, 0x5b, 0xb5, 0xae, 0xbe, 0xf0, 0x2e, 0x1a, 0xc0, 0xcb, 0x1b, 0xc0, 0x0a, 0x27,
-	0x7e, 0xe0, 0xcd, 0xfb, 0x5e, 0x3c, 0x0d, 0x3d, 0xd3, 0x4e, 0x5e, 0xa9, 0x9d, 0xbc, 0xbc, 0x9d,
-	0xbc, 0x47, 0x2b, 0x5e, 0xf1, 0x5a, 0x1c, 0xf4, 0x16, 0x6c, 0x9d, 0xd1, 0x28, 0x76, 0x6b, 0x36,
-	0xde, 0x6e, 0x91, 0xfa, 0x43, 0x1a, 0xc5, 0xd8, 0x52, 0xe8, 0x6d, 0xd8, 0x89, 0xa3, 0x24, 0x64,
-	0x5c, 0xb9, 0x60, 0xcb, 0xdc, 0x28, 0xb4, 0x8e, 0x2c, 0x8e, 0x73, 0xde, 0xd4, 0x30, 0x51, 0x54,
-	0x4e, 0x84, 0x59, 0x8d, 0x98, 0x4a, 0x6b, 0x58, 0x4f, 0x6b, 0xb8, 0xce, 0xa0, 0x1f, 0x1d, 0x78,
-	0xdd, 0xb7, 0x55, 0x79, 0x4c, 0x38, 0x09, 0xe9, 0x8c, 0x72, 0x7d, 0x94, 0xc5, 0xba, 0x66, 0x63,
-	0x3d, 0x7b, 0xb5, 0x0a, 0x0c, 0x37, 0x3a, 0xc7, 0xff, 0x14, 0x14, 0xbd, 0x0b, 0xfb, 0x45, 0x89,
-	0x9e, 0x53, 0xa9, 0xec, 0x59, 0xec, 0xb6, 0x2b, 0xdd, 0x1a, 0x5e, 0x27, 0x50, 0x13, 0xaa, 0x09,
-	0x1b, 0x2a, 0x75, 0x8c, 0x27, 0xee, 0x75, 0xdb, 0xa9, 0xc5, 0x1a, 0x75, 0xa1, 0x91, 0xb0, 0x01,
-	0xe1, 0x9c, 0xca, 0xa1, 0xe0, 0x9a, 0x72, 0xed, 0x36, 0xac, 0xca, 0x2a, 0x6c, 0x5a, 0x3e, 0x87,
-	0x8c, 0xa3, 0xbd, 0xb4, 0xe5, 0x4b, 0x90, 0xf1, 0x15, 0x13, 0xa5, 0xbe, 0x13, 0x32, 0x38, 0x22,
-	0x5a, 0x53, 0xc9, 0xdd, 0xfd, 0xd4, 0xd7, 0x0a, 0x8c, 0xee, 0xc0, 0x75, 0x2d, 0x89, 0x3f, 0x65,
-	0x3c, 0x7c, 0x4c, 0xf5, 0x99, 0x08, 0x5c, 0x64, 0x15, 0x57, 0x50, 0xb3, 0xcf, 0x3c, 0xc0, 0x11,
-	0x95, 0x33, 0xc2, 0x4d, 0x7e, 0x37, 0xec, 0x39, 0xad, 0x13, 0xe8, 0x1d, 0xd8, 0x2b, 0x40, 0xa1,
-	0x98, 0x29, 0xb1, 0x7b, 0xd3, 0xfa, 0x5d, 0xc3, 0x9b, 0x3f, 0x3b, 0x70, 0x6b, 0xf3, 0xa5, 0x43,
-	0x7b, 0x50, 0x99, 0xd2, 0x45, 0xfa, 0xda, 0x60, 0x23, 0xa2, 0x00, 0xae, 0xce, 0x49, 0x94, 0xd0,
-	0xec, 0x81, 0x79, 0xc5, 0x76, 0x5f, 0x0d, 0x8b, 0x53, 0xe7, 0x1f, 0x5f, 0xf9, 0xc8, 0xe9, 0x9c,
-	0xc0, 0x6b, 0x1b, 0x6f, 0x23, 0x6a, 0x01, 0xe4, 0xb5, 0x19, 0x8f, 0xb2, 0xdc, 0x4a, 0x88, 0xa9,
-	0x28, 0xe1, 0x82, 0x2f, 0xcc, 0xc1, 0x1f, 0x2b, 0x2a, 0x95, 0xcd, 0xb5, 0x8a, 0x57, 0xd0, 0xce,
-	0x2f, 0x0e, 0x6c, 0x99, 0x4b, 0x83, 0x5c, 0xd8, 0xf1, 0xcf, 0x88, 0x3e, 0xce, 0xdf, 0x55, 0x9c,
-	0x2f, 0x4d, 0xbb, 0x18, 0xf1, 0x19, 0x7d, 0xa1, 0xad, 0x93, 0x1a, 0x2e, 0xd6, 0xe8, 0x1e, 0xc0,
-	0x29, 0xe3, 0x44, 0x2e, 0x8e, 0x65, 0xa4, 0xdc, 0x8a, 0xed, 0xfd, 0x37, 0x2f, 0xdd, 0x46, 0x6f,
-	0x50, 0xf0, 0xe9, 0x1b, 0x56, 0x32, 0x68, 0xde, 0x83, 0xc6, 0x0a, 0xbd, 0xa1, 0xda, 0x37, 0xcb,
-	0xd5, 0xae, 0x95, 0xab, 0x73, 0x1b, 0xb6, 0xd3, 0x1b, 0x80, 0x10, 0x6c, 0x71, 0x32, 0xa3, 0x99,
-	0x99, 0x95, 0x3b, 0x9f, 0x40, 0xad, 0x78, 0xf0, 0x51, 0x1f, 0xc0, 0x17, 0x9c, 0x53, 0x5f, 0x0b,
-	0xa9, 0x5c, 0xc7, 0x26, 0x7a, 0x31, 0x18, 0x86, 0x39, 0x85, 0x4b, 0x5a, 0x9d, 0xbb, 0x50, 0x2b,
-	0x88, 0x4d, 0x11, 0x0c, 0xa6, 0x17, 0x71, 0x9e, 0x98, 0x95, 0x3b, 0x3f, 0x54, 0xa0, 0x34, 0x24,
-	0x36, 0x9a, 0xdd, 0x82, 0x6d, 0xa6, 0x54, 0x42, 0x65, 0x66, 0x98, 0xad, 0x50, 0x17, 0xaa, 0x7e,
-	0xc4, 0x28, 0xd7, 0xe3, 0x91, 0x9d, 0x43, 0xb5, 0xc1, 0xb5, 0xe5, 0xf9, 0x41, 0x75, 0x98, 0x61,
-	0xb8, 0x60, 0xd1, 0x21, 0xd4, 0xfd, 0x88, 0xe5, 0x44, 0x3a, 0x6e, 0x06, 0x8d, 0xe5, 0xf9, 0x41,
-	0x7d, 0x38, 0x19, 0x17, 0xfa, 0x65, 0x1d, 0x13, 0x54, 0xf9, 0x22, 0xce, 0x86, 0x4e, 0x0d, 0x67,
-	0x2b, 0x74, 0x02, 0xbb, 0x2c, 0x78, 0x26, 0xa6, 0x94, 0x0f, 0xed, 0x00, 0x76, 0xb7, 0x6d, 0x6d,
-	0xee, 0x6c, 0x98, 0x80, 0xde, 0xb8, 0xac, 0x68, 0x8f, 0x6b, 0xb0, 0xbf, 0x3c, 0x3f, 0xd8, 0x1d,
-	0x8f, 0x4a, 0x38, 0xbe, 0xec, 0xaf, 0xb9, 0x00, 0xb4, 0x6e, 0xb7, 0xe1, 0x98, 0x1f, 0x5f, 0xbe,
-	0x54, 0x1f, 0xfe, 0xeb, 0xa5, 0x4a, 0x7f, 0x10, 0x5e, 0xf1, 0x05, 0x32, 0xa3, 0xd8, 0xb3, 0xfe,
-	0x4b, 0xfd, 0xd1, 0xff, 0x1a, 0x1a, 0xf9, 0x44, 0x7d, 0x4a, 0xe5, 0x9c, 0xf9, 0x14, 0x7d, 0x0e,
-	0x95, 0x07, 0x54, 0xa3, 0x5b, 0x6b, 0x23, 0xd7, 0x7e, 0x33, 0x9a, 0xfb, 0x6b, 0x78, 0xc7, 0xfd,
-	0xfe, 0xf7, 0x3f, 0x7f, 0xba, 0x82, 0xd0, 0x9e, 0xfd, 0x3a, 0xcd, 0x0f, 0x8b, 0x6f, 0xcb, 0x60,
-	0xf8, 0xeb, 0xb2, 0xe5, 0xfc, 0xb6, 0x6c, 0x39, 0x7f, 0x2c, 0x5b, 0xce, 0x57, 0x1f, 0xfc, 0xbf,
-	0x2f, 0x54, 0x7a, 0x86, 0x85, 0x93, 0xd3, 0x6d, 0xfb, 0xe1, 0xb9, 0xfb, 0x77, 0x00, 0x00, 0x00,
-	0xff, 0xff, 0xfe, 0x6b, 0x22, 0xb7, 0xdf, 0x09, 0x00, 0x00,
+	// 1093 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0x1b, 0x45,
+	0x14, 0xd7, 0xc6, 0x69, 0x62, 0x3f, 0x37, 0x71, 0x32, 0x4d, 0xc3, 0x62, 0x15, 0xc7, 0xf8, 0x50,
+	0x19, 0x04, 0x6b, 0xe2, 0x0a, 0x81, 0x90, 0x2a, 0xc0, 0x76, 0xd5, 0x9a, 0x3a, 0x24, 0x6c, 0x93,
+	0x1e, 0x90, 0x50, 0x34, 0xd9, 0x1d, 0x36, 0x83, 0xd7, 0x33, 0xab, 0x99, 0x59, 0x53, 0xf7, 0xc8,
+	0x8d, 0x0b, 0x17, 0xf8, 0x08, 0x7c, 0x0a, 0x3e, 0x01, 0x47, 0x24, 0xee, 0x11, 0xb2, 0xf8, 0x20,
+	0x68, 0x66, 0xff, 0x64, 0x63, 0x1b, 0xa8, 0xd4, 0xdb, 0x9b, 0xdf, 0xef, 0xfd, 0xdb, 0x37, 0xef,
+	0xed, 0x1b, 0x68, 0x48, 0x22, 0xa6, 0x44, 0x74, 0x24, 0x51, 0x8a, 0xb2, 0x40, 0xe6, 0x82, 0x13,
+	0x09, 0xae, 0x38, 0xda, 0xf4, 0xc2, 0x58, 0x2a, 0x22, 0xea, 0x7b, 0x01, 0x0f, 0xb8, 0xc1, 0x3a,
+	0x5a, 0x4a, 0xe8, 0xfa, 0xbd, 0x80, 0xf3, 0x20, 0x24, 0x1d, 0x1c, 0xd1, 0x0e, 0x66, 0x8c, 0x2b,
+	0xac, 0x28, 0x67, 0xa9, 0x71, 0x7d, 0x14, 0x50, 0x75, 0x19, 0x5f, 0x38, 0x1e, 0x9f, 0x74, 0xb0,
+	0x30, 0xe6, 0xdf, 0x19, 0xe1, 0x7d, 0xcf, 0xef, 0x4c, 0xbb, 0x9d, 0x68, 0x1c, 0x68, 0x4b, 0xd9,
+	0xc1, 0x51, 0x14, 0x52, 0xcf, 0xd8, 0x76, 0xa6, 0x87, 0x38, 0x8c, 0x2e, 0xf1, 0x61, 0x27, 0x20,
+	0x8c, 0x08, 0xac, 0x88, 0x9f, 0x7a, 0xfb, 0xec, 0x7f, 0xbc, 0x2d, 0x7e, 0x09, 0xa7, 0xbe, 0xd7,
+	0xf1, 0x42, 0x4c, 0x27, 0x69, 0x3e, 0xad, 0x1a, 0x6c, 0x3d, 0x4b, 0xd9, 0xaf, 0x62, 0x22, 0x66,
+	0xad, 0x5f, 0x01, 0xca, 0x19, 0x82, 0xde, 0x84, 0x52, 0x2c, 0x42, 0xdb, 0x6a, 0x5a, 0xed, 0x4a,
+	0x6f, 0x73, 0x7e, 0x75, 0x50, 0x3a, 0x73, 0x47, 0xae, 0xc6, 0xd0, 0x07, 0x50, 0xf1, 0xc9, 0x8b,
+	0x3e, 0x67, 0xdf, 0xd2, 0xc0, 0x5e, 0x6b, 0x5a, 0xed, 0x6a, 0x17, 0x39, 0x69, 0x65, 0x9c, 0x41,
+	0xc6, 0xb8, 0xd7, 0x4a, 0xa8, 0x0f, 0xa0, 0xe3, 0xa7, 0x26, 0x25, 0x63, 0x72, 0x27, 0x37, 0x39,
+	0x1e, 0x0e, 0xfa, 0x09, 0xd5, 0xdb, 0x9e, 0x5f, 0x1d, 0xc0, 0xf5, 0xd9, 0x2d, 0x98, 0xa1, 0x26,
+	0x54, 0x71, 0x14, 0x8d, 0xf0, 0x05, 0x09, 0x9f, 0x92, 0x99, 0xbd, 0xae, 0x33, 0x73, 0x8b, 0x10,
+	0x7a, 0x0e, 0xbb, 0x82, 0x48, 0x1e, 0x0b, 0x8f, 0x1c, 0x4f, 0x89, 0x10, 0xd4, 0x27, 0xd2, 0xbe,
+	0xd5, 0x2c, 0xb5, 0xab, 0xdd, 0x76, 0x1e, 0x2d, 0xfb, 0x42, 0xc7, 0x5d, 0x54, 0x7d, 0xc4, 0x94,
+	0x98, 0xb9, 0xcb, 0x2e, 0x90, 0x03, 0x48, 0x2a, 0xac, 0x62, 0xd9, 0xc3, 0x7e, 0x40, 0x1e, 0x31,
+	0x7c, 0x11, 0x12, 0xdf, 0xde, 0x68, 0x5a, 0xed, 0xb2, 0xbb, 0x82, 0x41, 0x4f, 0xa0, 0x96, 0x74,
+	0xc2, 0xe7, 0x0c, 0x87, 0x33, 0x45, 0x3d, 0x69, 0x6f, 0x9a, 0x6f, 0x6e, 0xe4, 0x59, 0x3c, 0xbe,
+	0xc9, 0xa7, 0x9f, 0xbb, 0x68, 0x86, 0x5e, 0xc2, 0xce, 0x38, 0x96, 0x8a, 0x4f, 0xe8, 0x4b, 0x72,
+	0x1c, 0x99, 0x6e, 0xb2, 0xcb, 0xc6, 0xd5, 0x97, 0xce, 0x75, 0x03, 0x38, 0x59, 0x03, 0x18, 0xe1,
+	0xdc, 0xf3, 0x9d, 0x69, 0xd7, 0x89, 0xc6, 0x81, 0xa3, 0xdb, 0xc9, 0x29, 0xb4, 0x93, 0x93, 0xb5,
+	0x93, 0xf3, 0x74, 0xc1, 0xab, 0xbb, 0x14, 0x07, 0xbd, 0x0d, 0xeb, 0x97, 0x24, 0x8c, 0xec, 0x8a,
+	0x89, 0xb7, 0x95, 0xa7, 0xfe, 0x84, 0x84, 0x91, 0x6b, 0x28, 0xf4, 0x0e, 0x6c, 0x46, 0x61, 0x1c,
+	0x50, 0x26, 0x6d, 0x30, 0x65, 0xae, 0xe5, 0x5a, 0x27, 0x06, 0x77, 0x33, 0x5e, 0xd7, 0x30, 0x96,
+	0x44, 0x8c, 0xb8, 0x3e, 0x0d, 0xa8, 0x4c, 0x6a, 0x58, 0x4d, 0x6a, 0xb8, 0xcc, 0xa0, 0x9f, 0x2c,
+	0x78, 0xc3, 0x33, 0x55, 0x39, 0xc2, 0x0c, 0x07, 0x64, 0x42, 0x98, 0x3a, 0x49, 0x63, 0xdd, 0x36,
+	0xb1, 0x4e, 0x5f, 0xaf, 0x02, 0xfd, 0x95, 0xce, 0xdd, 0x7f, 0x0b, 0x8a, 0xde, 0x83, 0xdd, 0xbc,
+	0x44, 0xcf, 0x89, 0x90, 0xe6, 0x2e, 0xb6, 0x9a, 0xa5, 0x76, 0xc5, 0x5d, 0x26, 0x50, 0x1d, 0xca,
+	0x31, 0xed, 0x4b, 0x79, 0xe6, 0x8e, 0xec, 0x6d, 0xd3, 0xa9, 0xf9, 0x19, 0xb5, 0xa1, 0x16, 0xd3,
+	0x1e, 0x66, 0x8c, 0x88, 0x3e, 0x67, 0x8a, 0x30, 0x65, 0xd7, 0x8c, 0xca, 0x22, 0xac, 0x5b, 0x3e,
+	0x83, 0xb4, 0xa3, 0x9d, 0xa4, 0xe5, 0x0b, 0x90, 0xf6, 0x15, 0x61, 0x29, 0xbf, 0xe7, 0xc2, 0x3f,
+	0xc1, 0x4a, 0x11, 0xc1, 0xec, 0xdd, 0xc4, 0xd7, 0x02, 0x8c, 0xee, 0xc3, 0xb6, 0x12, 0xd8, 0x1b,
+	0x53, 0x16, 0x1c, 0x11, 0x75, 0xc9, 0x7d, 0x1b, 0x19, 0xc5, 0x05, 0x54, 0x7f, 0x67, 0x16, 0xe0,
+	0x84, 0x88, 0x09, 0x66, 0x3a, 0xbf, 0x3b, 0xe6, 0x9e, 0x96, 0x09, 0xf4, 0x2e, 0xec, 0xe4, 0x20,
+	0x97, 0x54, 0x97, 0xd8, 0xde, 0x33, 0x7e, 0x97, 0xf0, 0x85, 0x31, 0x72, 0x39, 0x57, 0x67, 0x22,
+	0xb4, 0xef, 0x1a, 0xed, 0x15, 0x4c, 0xfd, 0x17, 0x0b, 0xf6, 0x57, 0x0f, 0x29, 0xda, 0x81, 0xd2,
+	0x98, 0xcc, 0x92, 0xbf, 0x93, 0xab, 0x45, 0xe4, 0xc3, 0xad, 0x29, 0x0e, 0x63, 0x92, 0xfe, 0x90,
+	0x5e, 0x73, 0x3c, 0x16, 0xc3, 0xba, 0x89, 0xf3, 0x4f, 0xd6, 0x3e, 0xb6, 0x5a, 0xe7, 0x70, 0x77,
+	0xe5, 0xf4, 0xa2, 0x06, 0x40, 0x56, 0xcb, 0xe1, 0x20, 0xcd, 0xad, 0x80, 0xe8, 0x1b, 0xc0, 0x8c,
+	0xb3, 0x99, 0x6e, 0x94, 0x33, 0x49, 0x84, 0x34, 0xb9, 0x96, 0xdd, 0x05, 0xb4, 0xf5, 0x9b, 0x05,
+	0xeb, 0x7a, 0xc8, 0x90, 0x0d, 0x9b, 0xde, 0x25, 0x36, 0x55, 0x4a, 0xbc, 0x65, 0x47, 0xdd, 0x5e,
+	0x5a, 0x3c, 0x25, 0x2f, 0x94, 0x71, 0x52, 0x71, 0xf3, 0x33, 0x7a, 0x08, 0x70, 0x41, 0x19, 0x16,
+	0xb3, 0x33, 0x11, 0x4a, 0xbb, 0x64, 0x66, 0xe5, 0xad, 0x1b, 0xd3, 0xeb, 0xf4, 0x72, 0x3e, 0xf9,
+	0xe7, 0x15, 0x0c, 0xea, 0x0f, 0xa1, 0xb6, 0x40, 0xaf, 0xa8, 0xf6, 0x5e, 0xb1, 0xda, 0x95, 0x62,
+	0x75, 0xee, 0xc1, 0x46, 0x32, 0x31, 0x08, 0xc1, 0x3a, 0xc3, 0x13, 0x92, 0x9a, 0x19, 0xb9, 0xf5,
+	0x29, 0x54, 0xf2, 0x05, 0x81, 0xba, 0x00, 0x1e, 0x67, 0x8c, 0x78, 0x8a, 0x0b, 0x69, 0x5b, 0x26,
+	0xd1, 0xeb, 0x45, 0xd2, 0xcf, 0x28, 0xb7, 0xa0, 0xd5, 0x7a, 0x00, 0x95, 0x9c, 0x58, 0x15, 0x41,
+	0x63, 0x6a, 0x16, 0x65, 0x89, 0x19, 0xb9, 0xf5, 0x63, 0x09, 0x0a, 0x4b, 0x65, 0xa5, 0xd9, 0x3e,
+	0x6c, 0x50, 0x29, 0x63, 0x22, 0x52, 0xc3, 0xf4, 0x84, 0xda, 0x50, 0xf6, 0x42, 0x4a, 0x98, 0x1a,
+	0x0e, 0xcc, 0xde, 0xaa, 0xf4, 0x6e, 0xcf, 0xaf, 0x0e, 0xca, 0xfd, 0x14, 0x73, 0x73, 0x16, 0x1d,
+	0x42, 0xd5, 0x0b, 0x69, 0x46, 0x24, 0xeb, 0xa9, 0x57, 0x9b, 0x5f, 0x1d, 0x54, 0xfb, 0xa3, 0x61,
+	0xae, 0x5f, 0xd4, 0xd1, 0x41, 0xa5, 0xc7, 0xa3, 0x74, 0x49, 0x55, 0xdc, 0xf4, 0x84, 0xce, 0x61,
+	0x8b, 0xfa, 0xa7, 0x7c, 0x4c, 0x58, 0xdf, 0x2c, 0x6c, 0x7b, 0xc3, 0xd4, 0xe6, 0xfe, 0x8a, 0x8d,
+	0xe9, 0x0c, 0x8b, 0x8a, 0xe6, 0xba, 0x7a, 0xbb, 0xf3, 0xab, 0x83, 0xad, 0xe1, 0xa0, 0x80, 0xbb,
+	0x37, 0xfd, 0xd5, 0x67, 0x80, 0x96, 0xed, 0x56, 0x5c, 0xf3, 0xd1, 0xcd, 0xa1, 0xfa, 0xe8, 0x3f,
+	0x87, 0x2a, 0x79, 0x71, 0x38, 0xf9, 0x93, 0x49, 0xaf, 0x6e, 0xc7, 0xf8, 0x2f, 0xf4, 0x47, 0xf7,
+	0x1b, 0xa8, 0x65, 0x1b, 0xf8, 0x19, 0x11, 0x53, 0xea, 0x11, 0xf4, 0x05, 0x94, 0x1e, 0x13, 0x85,
+	0xf6, 0x97, 0x56, 0xb4, 0x79, 0x96, 0xd4, 0x77, 0x97, 0xf0, 0x96, 0xfd, 0xc3, 0x9f, 0x7f, 0xff,
+	0xbc, 0x86, 0xd0, 0x8e, 0x79, 0x6a, 0x4d, 0x0f, 0xf3, 0x67, 0x4e, 0xaf, 0xff, 0xfb, 0xbc, 0x61,
+	0xfd, 0x31, 0x6f, 0x58, 0x7f, 0xcd, 0x1b, 0xd6, 0xd7, 0x1f, 0xbe, 0xda, 0x93, 0x2b, 0xb9, 0xc3,
+	0xdc, 0xc9, 0xc5, 0x86, 0x79, 0x20, 0x3d, 0xf8, 0x27, 0x00, 0x00, 0xff, 0xff, 0x89, 0x27, 0x16,
+	0x80, 0x0f, 0x0a, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -855,6 +864,15 @@ func (m *Settings) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if len(m.StatusBadgeRootUrl) > 0 {
+		i -= len(m.StatusBadgeRootUrl)
+		copy(dAtA[i:], m.StatusBadgeRootUrl)
+		i = encodeVarintSettings(dAtA, i, uint64(len(m.StatusBadgeRootUrl)))
+		i--
+		dAtA[i] = 0x1
+		i--
+		dAtA[i] = 0xaa
+	}
 	if len(m.UiBannerPosition) > 0 {
 		i -= len(m.UiBannerPosition)
 		copy(dAtA[i:], m.UiBannerPosition)
@@ -1508,6 +1526,10 @@ func (m *Settings) Size() (n int) {
 	if l > 0 {
 		n += 2 + l + sovSettings(uint64(l))
 	}
+	l = len(m.StatusBadgeRootUrl)
+	if l > 0 {
+		n += 2 + l + sovSettings(uint64(l))
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -2473,6 +2495,38 @@ func (m *Settings) Unmarshal(dAtA []byte) error {
 			}
 			m.UiBannerPosition = string(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
+		case 21:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field StatusBadgeRootUrl", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowSettings
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthSettings
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthSettings
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.StatusBadgeRootUrl = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipSettings(dAtA[iNdEx:])
diff --git a/pkg/apiclient/version/version.pb.go b/pkg/apiclient/version/version.pb.go
index f52bf8e4b..78a7be47c 100644
--- a/pkg/apiclient/version/version.pb.go
+++ b/pkg/apiclient/version/version.pb.go
@@ -11,11 +11,11 @@ import (
 	context "context"
 	fmt "fmt"
 	proto "github.com/gogo/protobuf/proto"
-	empty "github.com/golang/protobuf/ptypes/empty"
 	_ "google.golang.org/genproto/googleapis/api/annotations"
 	grpc "google.golang.org/grpc"
 	codes "google.golang.org/grpc/codes"
 	status "google.golang.org/grpc/status"
+	emptypb "google.golang.org/protobuf/types/known/emptypb"
 	io "io"
 	math "math"
 	math_bits "math/bits"
@@ -225,7 +225,7 @@ const _ = grpc.SupportPackageIsVersion4
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
 type VersionServiceClient interface {
 	// Version returns version information of the API server
-	Version(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*VersionMessage, error)
+	Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionMessage, error)
 }
 
 type versionServiceClient struct {
@@ -236,7 +236,7 @@ func NewVersionServiceClient(cc *grpc.ClientConn) VersionServiceClient {
 	return &versionServiceClient{cc}
 }
 
-func (c *versionServiceClient) Version(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*VersionMessage, error) {
+func (c *versionServiceClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionMessage, error) {
 	out := new(VersionMessage)
 	err := c.cc.Invoke(ctx, "/version.VersionService/Version", in, out, opts...)
 	if err != nil {
@@ -248,14 +248,14 @@ func (c *versionServiceClient) Version(ctx context.Context, in *empty.Empty, opt
 // VersionServiceServer is the server API for VersionService service.
 type VersionServiceServer interface {
 	// Version returns version information of the API server
-	Version(context.Context, *empty.Empty) (*VersionMessage, error)
+	Version(context.Context, *emptypb.Empty) (*VersionMessage, error)
 }
 
 // UnimplementedVersionServiceServer can be embedded to have forward compatible implementations.
 type UnimplementedVersionServiceServer struct {
 }
 
-func (*UnimplementedVersionServiceServer) Version(ctx context.Context, req *empty.Empty) (*VersionMessage, error) {
+func (*UnimplementedVersionServiceServer) Version(ctx context.Context, req *emptypb.Empty) (*VersionMessage, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method Version not implemented")
 }
 
@@ -264,7 +264,7 @@ func RegisterVersionServiceServer(s *grpc.Server, srv VersionServiceServer) {
 }
 
 func _VersionService_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(empty.Empty)
+	in := new(emptypb.Empty)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
@@ -276,7 +276,7 @@ func _VersionService_Version_Handler(srv interface{}, ctx context.Context, dec f
 		FullMethod: "/version.VersionService/Version",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(VersionServiceServer).Version(ctx, req.(*empty.Empty))
+		return srv.(VersionServiceServer).Version(ctx, req.(*emptypb.Empty))
 	}
 	return interceptor(ctx, in, info, handler)
 }
diff --git a/pkg/apiclient/version/version.pb.gw.go b/pkg/apiclient/version/version.pb.gw.go
index da205bd11..e1fabc754 100644
--- a/pkg/apiclient/version/version.pb.gw.go
+++ b/pkg/apiclient/version/version.pb.gw.go
@@ -15,7 +15,6 @@ import (
 
 	"github.com/golang/protobuf/descriptor"
 	"github.com/golang/protobuf/proto"
-	"github.com/golang/protobuf/ptypes/empty"
 	"github.com/grpc-ecosystem/grpc-gateway/runtime"
 	"github.com/grpc-ecosystem/grpc-gateway/utilities"
 	"google.golang.org/grpc"
@@ -23,6 +22,7 @@ import (
 	"google.golang.org/grpc/grpclog"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
+	"google.golang.org/protobuf/types/known/emptypb"
 )
 
 // Suppress "imported and not used" errors
@@ -35,7 +35,7 @@ var _ = descriptor.ForMessage
 var _ = metadata.Join
 
 func request_VersionService_Version_0(ctx context.Context, marshaler runtime.Marshaler, client VersionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
-	var protoReq empty.Empty
+	var protoReq emptypb.Empty
 	var metadata runtime.ServerMetadata
 
 	msg, err := client.Version(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
@@ -44,7 +44,7 @@ func request_VersionService_Version_0(ctx context.Context, marshaler runtime.Mar
 }
 
 func local_request_VersionService_Version_0(ctx context.Context, marshaler runtime.Marshaler, server VersionServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
-	var protoReq empty.Empty
+	var protoReq emptypb.Empty
 	var metadata runtime.ServerMetadata
 
 	msg, err := server.Version(ctx, &protoReq)
diff --git a/pkg/apis/application/v1alpha1/app_project_types.go b/pkg/apis/application/v1alpha1/app_project_types.go
index 05d4ecd38..1a3253576 100644
--- a/pkg/apis/application/v1alpha1/app_project_types.go
+++ b/pkg/apis/application/v1alpha1/app_project_types.go
@@ -58,7 +58,7 @@ func (p *AppProject) GetRoleByName(name string) (*ProjectRole, int, error) {
 	return nil, -1, fmt.Errorf("role '%s' does not exist in project '%s'", name, p.Name)
 }
 
-// GetJWTToken looks up the index of a JWTToken in a project by id (new token), if not then by the issue at time (old token)
+// GetJWTTokenFromSpec looks up the index of a JWTToken in a project by id (new token), if not then by the issue at time (old token)
 func (p *AppProject) GetJWTTokenFromSpec(roleName string, issuedAt int64, id string) (*JWTToken, int, error) {
 	// This is for backward compatibility. In the oder version, JWTTokens are stored under spec.role
 	role, _, err := p.GetRoleByName(roleName)
diff --git a/pkg/apis/application/v1alpha1/cluster_constants.go b/pkg/apis/application/v1alpha1/cluster_constants.go
index 8502df712..13895eaf6 100644
--- a/pkg/apis/application/v1alpha1/cluster_constants.go
+++ b/pkg/apis/application/v1alpha1/cluster_constants.go
@@ -1,8 +1,10 @@
 package v1alpha1
 
 import (
-	"os"
-	"strconv"
+	"math"
+	"time"
+
+	"github.com/argoproj/argo-cd/v2/util/env"
 )
 
 const (
@@ -19,39 +21,44 @@ const (
 
 	// EnvK8sClientMaxIdleConnections is the number of max idle connections in K8s REST client HTTP transport (default: 500)
 	EnvK8sClientMaxIdleConnections = "ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS"
+
+	// EnvK8sTCPTimeout is the duration for TCP timeouts when communicating with K8s API servers
+	EnvK8sTCPTimeout = "ARGOCD_K8S_TCP_TIMEOUT"
+
+	// EnvK8sTCPKeepalive is the interval for TCP keep alive probes to be sent when communicating with K8s API servers
+	EnvK8sTCPKeepAlive = "ARGOCD_K8S_TCP_KEEPALIVE"
+
+	// EnvK8sTLSHandshakeTimeout is the duration for TLS handshake timeouts when establishing connections to K8s API servers
+	EnvK8sTLSHandshakeTimeout = "ARGOCD_K8S_TLS_HANDSHAKE_TIMEOUT"
+
+	// EnvK8sTCPIdleConnTimeout is the duration when idle TCP connection to the K8s API servers should timeout
+	EnvK8sTCPIdleConnTimeout = "ARGOCD_K8S_TCP_IDLE_TIMEOUT"
 )
 
-// Constants associated with the Cluster API
+// Configuration variables associated with the Cluster API
 var (
 
 	// K8sClientConfigQPS controls the QPS to be used in K8s REST client configs
-	K8sClientConfigQPS float32 = 50
+	K8sClientConfigQPS float32 = env.ParseFloatFromEnv(EnvK8sClientQPS, 50, 0, math.MaxFloat32)
 
 	// K8sClientConfigBurst controls the burst to be used in K8s REST client configs
-	K8sClientConfigBurst int = 100
+	K8sClientConfigBurst int = env.ParseNumFromEnv(EnvK8sClientBurst, int(2*K8sClientConfigQPS), 0, math.MaxInt32)
 
 	// K8sMaxIdleConnections controls the number of max idle connections in K8s REST client HTTP transport
-	K8sMaxIdleConnections = 500
-)
+	K8sMaxIdleConnections = env.ParseNumFromEnv(EnvK8sClientMaxIdleConnections, 500, 0, math.MaxInt32)
 
-func init() {
-	if envQPS := os.Getenv(EnvK8sClientQPS); envQPS != "" {
-		if qps, err := strconv.ParseFloat(envQPS, 32); err == nil {
-			K8sClientConfigQPS = float32(qps)
-		}
-	}
-	if envBurst := os.Getenv(EnvK8sClientBurst); envBurst != "" {
-		if burst, err := strconv.Atoi(envBurst); err == nil {
-			K8sClientConfigBurst = burst
-		}
-	} else {
-		K8sClientConfigBurst = 2 * int(K8sClientConfigQPS)
-	}
-
-	if envMaxConn := os.Getenv(EnvK8sClientMaxIdleConnections); envMaxConn != "" {
-		if maxConn, err := strconv.Atoi(envMaxConn); err == nil {
-			K8sMaxIdleConnections = maxConn
-		}
-	}
-
-}
+	// K8sTLSHandshakeTimeout defines the maximum duration to wait for a TLS handshake to complete
+	K8sTLSHandshakeTimeout = env.ParseDurationFromEnv(EnvK8sTLSHandshakeTimeout, 10*time.Second, 0, math.MaxInt32)
+
+	// K8sTCPTimeout defines the TCP timeout to use when performing K8s API requests
+	K8sTCPTimeout = env.ParseDurationFromEnv(EnvK8sTCPTimeout, 30*time.Second, 0, math.MaxInt32)
+
+	// K8sTCPKeepAlive defines the interval for sending TCP keep alive to K8s API server
+	K8sTCPKeepAlive = env.ParseDurationFromEnv(EnvK8sTCPKeepAlive, 30*time.Second, 0, math.MaxInt32)
+
+	// K8sTCPIdleConnTimeout defines the duration for keeping idle TCP connections to the K8s API server
+	K8sTCPIdleConnTimeout = env.ParseDurationFromEnv(EnvK8sTCPIdleConnTimeout, 5*time.Minute, 0, math.MaxInt32)
+
+	// K8sServerSideTimeout defines which server side timeout to send with each API request
+	K8sServerSideTimeout = env.ParseDurationFromEnv(EnvK8sTCPTimeout, 32*time.Second, 0, math.MaxInt32)
+)
diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go
index 304a8cefd..3ae40f032 100644
--- a/pkg/apis/application/v1alpha1/types.go
+++ b/pkg/apis/application/v1alpha1/types.go
@@ -2353,18 +2353,19 @@ func SetK8SConfigDefaults(config *rest.Config) error {
 	}
 
 	dial := (&net.Dialer{
-		Timeout:   30 * time.Second,
-		KeepAlive: 30 * time.Second,
+		Timeout:   K8sTCPTimeout,
+		KeepAlive: K8sTCPKeepAlive,
 	}).DialContext
 	transport := utilnet.SetTransportDefaults(&http.Transport{
 		Proxy:               http.ProxyFromEnvironment,
-		TLSHandshakeTimeout: 10 * time.Second,
+		TLSHandshakeTimeout: K8sTLSHandshakeTimeout,
 		TLSClientConfig:     tlsConfig,
 		MaxIdleConns:        K8sMaxIdleConnections,
 		MaxIdleConnsPerHost: K8sMaxIdleConnections,
 		MaxConnsPerHost:     K8sMaxIdleConnections,
 		DialContext:         dial,
 		DisableCompression:  config.DisableCompression,
+		IdleConnTimeout:     K8sTCPIdleConnTimeout,
 	})
 	tr, err := rest.HTTPWrappersForConfig(config, transport)
 	if err != nil {
@@ -2376,6 +2377,9 @@ func SetK8SConfigDefaults(config *rest.Config) error {
 	config.AuthProvider = nil
 	config.ExecProvider = nil
 
+	// Set server-side timeout
+	config.Timeout = K8sServerSideTimeout
+
 	config.Transport = tr
 	return nil
 }
@@ -2459,6 +2463,7 @@ func (c *Cluster) RawRestConfig() *rest.Config {
 	if err != nil {
 		panic(fmt.Sprintf("Unable to create K8s REST config: %v", err))
 	}
+	config.Timeout = K8sServerSideTimeout
 	return config
 }
 
diff --git a/resource_customizations/apps.openshift.io/DeploymentConfig/health.lua b/resource_customizations/apps.openshift.io/DeploymentConfig/health.lua
index 4fdc157dc..6270802a7 100644
--- a/resource_customizations/apps.openshift.io/DeploymentConfig/health.lua
+++ b/resource_customizations/apps.openshift.io/DeploymentConfig/health.lua
@@ -1,13 +1,13 @@
 health_check = {}
 if obj.status ~= nil then
-  if obj.status.conditions ~= nil then
+  if obj.status.conditions ~= nil and obj.status.replicas ~= nil then
     numTrue = 0
     for i, condition in pairs(obj.status.conditions) do
       if (condition.type == "Available" or condition.type == "Progressing") and condition.status == "True" then
         numTrue = numTrue + 1
       end
     end
-    if numTrue == 2 then
+    if numTrue == 2 or obj.status.replicas == 0 then
       health_check.status = "Healthy"
       health_check.message = "replication controller successfully rolled out"
       return health_check
diff --git a/server/settings/settings.go b/server/settings/settings.go
index 10f01cddb..9211d1548 100644
--- a/server/settings/settings.go
+++ b/server/settings/settings.go
@@ -89,6 +89,7 @@ func (s *Server) Get(ctx context.Context, q *settingspkg.SettingsQuery) (*settin
 		AppLabelKey:        appInstanceLabelKey,
 		ResourceOverrides:  overrides,
 		StatusBadgeEnabled: argoCDSettings.StatusBadgeEnabled,
+		StatusBadgeRootUrl: argoCDSettings.StatusBadgeRootUrl,
 		KustomizeOptions: &v1alpha1.KustomizeOptions{
 			BuildOptions: argoCDSettings.KustomizeBuildOptions,
 		},
diff --git a/server/settings/settings.proto b/server/settings/settings.proto
index fe5e9443a..138f594a4 100644
--- a/server/settings/settings.proto
+++ b/server/settings/settings.proto
@@ -37,6 +37,7 @@ message Settings {
     string trackingMethod = 18;
     bool uiBannerPermanent = 19;
     string uiBannerPosition = 20;
+    string statusBadgeRootUrl = 21;
 }
 
 message GoogleAnalyticsConfig {
diff --git a/ui/src/app/applications/components/application-details/application-details.scss b/ui/src/app/applications/components/application-details/application-details.scss
index c73fd9537..9b714f411 100644
--- a/ui/src/app/applications/components/application-details/application-details.scss
+++ b/ui/src/app/applications/components/application-details/application-details.scss
@@ -8,7 +8,7 @@ $header: 120px;
         position: fixed;
         left: 60px;
         right: 0;
-        z-index: 2;
+        z-index: 3;
         @media screen and (max-width: map-get($breakpoints, xlarge)) {
             top: 150px;
         }
@@ -52,6 +52,7 @@ $header: 120px;
         border-radius: 5px;
         padding: 5px 5px;
         font-size: 0.6em;
+        z-index: 1;
     }
 
     &__tab-content-full-height {
@@ -209,7 +210,7 @@ $header: 120px;
 
     .graph-options-panel {
         margin-left: 10px;
-        z-index: 10;
+        z-index: 1;
         padding: 5px;
         display: inline-block;
         background-color: $argo-color-gray-1;
diff --git a/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx b/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx
index 829a905c0..269c99fe0 100644
--- a/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx
+++ b/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx
@@ -89,6 +89,7 @@ const syncOptions: Array<(props: ApplicationSyncOptionProps) => React.ReactNode>
     props => booleanOption('CreateNamespace', 'Auto-Create Namespace', false, props, false),
     props => booleanOption('PruneLast', 'Prune Last', false, props, false),
     props => booleanOption('ApplyOutOfSyncOnly', 'Apply Out of Sync Only', false, props, false),
+    props => booleanOption('RespectIgnoreDifferences', 'Respect Ignore Differences', false, props, false),
     props => selectOption('PrunePropagationPolicy', 'Prune Propagation Policy', 'foreground', ['foreground', 'background', 'orphan'], props)
 ];
 
diff --git a/ui/src/app/applications/components/filter/filter.scss b/ui/src/app/applications/components/filter/filter.scss
index 1cdc939af..4651ca381 100644
--- a/ui/src/app/applications/components/filter/filter.scss
+++ b/ui/src/app/applications/components/filter/filter.scss
@@ -92,7 +92,7 @@
         padding: 10px;
         overflow-y: hidden;
         overflow-x: hidden;
-        z-index: 1;
+        z-index: 2;
 
         position: fixed;
         width: 240px;
diff --git a/ui/src/app/shared/components/badge-panel/badge-panel.tsx b/ui/src/app/shared/components/badge-panel/badge-panel.tsx
index 0c3ef11d2..ad6a4f6c1 100644
--- a/ui/src/app/shared/components/badge-panel/badge-panel.tsx
+++ b/ui/src/app/shared/components/badge-panel/badge-panel.tsx
@@ -9,66 +9,67 @@ require('./badge-panel.scss');
 export const BadgePanel = ({app, project}: {app?: string; project?: string}) => {
     const [badgeType, setBadgeType] = React.useState('URL');
     const context = React.useContext(Context);
-    const root = `${location.protocol}//${location.host}${context.baseHref}`;
+    if (!app && !project) {
+        throw new Error('Either app or project property must be specified');
+    }
 
-    let badgeURL = '';
-    let entityURL = '';
-    let alt = '';
-    if (app) {
-        badgeURL = `${root}api/badge?name=${app}&revision=true`;
-        entityURL = `${root}applications/${app}`;
-        alt = 'App Status';
-    } else if (project) {
-        badgeURL = `${root}api/badge?project=${project}&revision=true`;
-        entityURL = `${root}projects/${project}`;
-        alt = 'Project Status';
-    } else {
-        throw new Error('Either app of project property must be specified');
+    function badgeContent(statusBadgeRootUrl: string) {
+        const root = statusBadgeRootUrl ? statusBadgeRootUrl : `${location.protocol}//${location.host}${context.baseHref}`;
+        let badgeURL = '';
+        let entityURL = '';
+        let alt = '';
+        if (app) {
+            badgeURL = `${root}api/badge?name=${app}&revision=true`;
+            entityURL = `${root}applications/${app}`;
+            alt = 'App Status';
+        } else if (project) {
+            badgeURL = `${root}api/badge?project=${project}&revision=true`;
+            entityURL = `${root}projects/${project}`;
+            alt = 'Project Status';
+        }
+        return (
+            <div className='white-box'>
+                <div className='white-box__details'>
+                    <p>STATUS BADGE</p>
+                    <p>
+                        <img src={badgeURL} />{' '}
+                    </p>
+                    <div className='white-box__details-row'>
+                        <DropDownMenu
+                            anchor={() => (
+                                <p>
+                                    {badgeType} <i className='fa fa-caret-down' />
+                                </p>
+                            )}
+                            items={['URL', 'Markdown', 'Textile', 'Rdoc', 'AsciiDoc'].map(type => ({title: type, action: () => setBadgeType(type)}))}
+                        />
+                        <textarea
+                            onClick={e => (e.target as HTMLInputElement).select()}
+                            className='badge-panel'
+                            readOnly={true}
+                            value={
+                                badgeType === 'URL'
+                                    ? badgeURL
+                                    : badgeType === 'Markdown'
+                                    ? `[![${alt}](${badgeURL})](${entityURL})`
+                                    : badgeType === 'Textile'
+                                    ? `!${badgeURL}!:${entityURL}`
+                                    : badgeType === 'Rdoc'
+                                    ? `{<img src="${badgeURL}" alt="${alt}" />}[${entityURL}]`
+                                    : badgeType === 'AsciiDoc'
+                                    ? `image:${badgeURL}["${alt}", link="${entityURL}"]`
+                                    : ''
+                            }
+                        />
+                    </div>
+                </div>
+            </div>
+        );
     }
 
     return (
         <DataLoader load={() => services.authService.settings()}>
-            {settings =>
-                (settings.statusBadgeEnabled && (
-                    <div className='white-box'>
-                        <div className='white-box__details'>
-                            <p>STATUS BADGE</p>
-                            <p>
-                                <img src={badgeURL} />{' '}
-                            </p>
-                            <div className='white-box__details-row'>
-                                <DropDownMenu
-                                    anchor={() => (
-                                        <p>
-                                            {badgeType} <i className='fa fa-caret-down' />
-                                        </p>
-                                    )}
-                                    items={['URL', 'Markdown', 'Textile', 'Rdoc', 'AsciiDoc'].map(type => ({title: type, action: () => setBadgeType(type)}))}
-                                />
-                                <textarea
-                                    onClick={e => (e.target as HTMLInputElement).select()}
-                                    className='badge-panel'
-                                    readOnly={true}
-                                    value={
-                                        badgeType === 'URL'
-                                            ? badgeURL
-                                            : badgeType === 'Markdown'
-                                            ? `[![${alt}](${badgeURL})](${entityURL})`
-                                            : badgeType === 'Textile'
-                                            ? `!${badgeURL}!:${entityURL}`
-                                            : badgeType === 'Rdoc'
-                                            ? `{<img src="${badgeURL}" alt="${alt}" />}[${entityURL}]`
-                                            : badgeType === 'AsciiDoc'
-                                            ? `image:${badgeURL}["${alt}", link="${entityURL}"]`
-                                            : ''
-                                    }
-                                />
-                            </div>
-                        </div>
-                    </div>
-                )) ||
-                null
-            }
+            {settings => (settings.statusBadgeEnabled && <div>{badgeContent(settings.statusBadgeRootUrl)}</div>) || null}
         </DataLoader>
     );
 };
diff --git a/ui/src/app/shared/models.ts b/ui/src/app/shared/models.ts
index 9b3fef919..4da48c95a 100644
--- a/ui/src/app/shared/models.ts
+++ b/ui/src/app/shared/models.ts
@@ -429,6 +429,7 @@ export interface Plugin {
 export interface AuthSettings {
     url: string;
     statusBadgeEnabled: boolean;
+    statusBadgeRootUrl: string;
     googleAnalytics: {
         trackingID: string;
         anonymizeUsers: boolean;
diff --git a/util/db/cluster.go b/util/db/cluster.go
index 64e8db358..3bde48e06 100644
--- a/util/db/cluster.go
+++ b/util/db/cluster.go
@@ -61,6 +61,11 @@ func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
 	clusterList := appv1.ClusterList{
 		Items: make([]appv1.Cluster, 0),
 	}
+	settings, err := db.settingsMgr.GetSettings()
+	if err != nil {
+		return nil, err
+	}
+	inClusterEnabled := settings.InClusterEnabled
 	hasInClusterCredentials := false
 	for _, clusterSecret := range clusterSecrets {
 		cluster, err := secretToCluster(clusterSecret)
@@ -68,12 +73,18 @@ func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
 			log.Errorf("could not unmarshal cluster secret %s", clusterSecret.Name)
 			continue
 		}
-		clusterList.Items = append(clusterList.Items, *cluster)
 		if cluster.Server == appv1.KubernetesInternalAPIServerAddr {
-			hasInClusterCredentials = true
+			if inClusterEnabled {
+				hasInClusterCredentials = true
+				clusterList.Items = append(clusterList.Items, *cluster)
+			} else {
+				log.Errorf("failed to add cluster %q to cluster list: in-cluster server address is disabled in Argo CD settings", cluster.Name)
+			}
+		} else {
+			clusterList.Items = append(clusterList.Items, *cluster)
 		}
 	}
-	if !hasInClusterCredentials {
+	if inClusterEnabled && !hasInClusterCredentials {
 		clusterList.Items = append(clusterList.Items, *db.getLocalCluster())
 	}
 	return &clusterList, nil
@@ -81,6 +92,13 @@ func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
 
 // CreateCluster creates a cluster
 func (db *db) CreateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Cluster, error) {
+	settings, err := db.settingsMgr.GetSettings()
+	if err != nil {
+		return nil, err
+	}
+	if c.Server == appv1.KubernetesInternalAPIServerAddr && !settings.InClusterEnabled {
+		return nil, status.Errorf(codes.InvalidArgument, "cannot register cluster: in-cluster has been disabled")
+	}
 	secName, err := URIToSecretName("cluster", c.Server)
 	if err != nil {
 		return nil, err
diff --git a/util/db/mocks/ArgoDB.go b/util/db/mocks/ArgoDB.go
index fb6035462..eed84975d 100644
--- a/util/db/mocks/ArgoDB.go
+++ b/util/db/mocks/ArgoDB.go
@@ -5,10 +5,10 @@ package mocks
 import (
 	context "context"
 
-	db "github.com/argoproj/argo-cd/v2/util/db"
 	mock "github.com/stretchr/testify/mock"
 
 	v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	db "github.com/argoproj/argo-cd/v2/util/db"
 )
 
 // ArgoDB is an autogenerated mock type for the ArgoDB type
diff --git a/util/env/env.go b/util/env/env.go
index 19243e882..c86a5a736 100644
--- a/util/env/env.go
+++ b/util/env/env.go
@@ -64,9 +64,38 @@ func ParseInt64FromEnv(env string, defaultValue, min, max int64) int64 {
 	return num
 }
 
+// Helper function to parse a float32 from an environment variable. Returns a
+// default if env is not set, is not parseable to a number, exceeds max (if
+// max is greater than 0) or is less than min (and min is greater than 0).
+//
+// nolint:unparam
+func ParseFloatFromEnv(env string, defaultValue, min, max float32) float32 {
+	str := os.Getenv(env)
+	if str == "" {
+		return defaultValue
+	}
+
+	num, err := strconv.ParseFloat(str, 32)
+	if err != nil {
+		log.Warnf("Could not parse '%s' as a float32 from environment %s", str, env)
+		return defaultValue
+	}
+	if float32(num) < min {
+		log.Warnf("Value in %s is %f, which is less than minimum %f allowed", env, num, min)
+		return defaultValue
+	}
+	if float32(num) > max {
+		log.Warnf("Value in %s is %f, which is greater than maximum %f allowed", env, num, max)
+		return defaultValue
+	}
+	return float32(num)
+}
+
 // Helper function to parse a time duration from an environment variable. Returns a
 // default if env is not set, is not parseable to a duration, exceeds max (if
 // max is greater than 0) or is less than min.
+//
+// nolinit:unparam
 func ParseDurationFromEnv(env string, defaultValue, min, max time.Duration) time.Duration {
 	str := os.Getenv(env)
 	if str == "" {
@@ -99,6 +128,8 @@ func StringFromEnv(env string, defaultValue string) string {
 
 // ParseBoolFromEnv retrieves a boolean value from given environment envVar.
 // Returns default value if envVar is not set.
+//
+// nolinit:unparam
 func ParseBoolFromEnv(envVar string, defaultValue bool) bool {
 	if val := os.Getenv(envVar); val != "" {
 		if strings.ToLower(val) == "true" {
diff --git a/util/git/mocks/Client.go b/util/git/mocks/Client.go
index 963817108..4169ce08e 100644
--- a/util/git/mocks/Client.go
+++ b/util/git/mocks/Client.go
@@ -3,8 +3,9 @@
 package mocks
 
 import (
-	git "github.com/argoproj/argo-cd/v2/util/git"
 	mock "github.com/stretchr/testify/mock"
+
+	git "github.com/argoproj/argo-cd/v2/util/git"
 )
 
 // Client is an autogenerated mock type for the Client type
diff --git a/util/settings/settings.go b/util/settings/settings.go
index 71e731ac2..bfddf3ff3 100644
--- a/util/settings/settings.go
+++ b/util/settings/settings.go
@@ -48,6 +48,8 @@ type ArgoCDSettings struct {
 	URL string `json:"url,omitempty"`
 	// Indicates if status badge is enabled or not.
 	StatusBadgeEnabled bool `json:"statusBadgeEnable"`
+	// Indicates if status badge custom root URL should be used.
+	StatusBadgeRootUrl string `json:"statusBadgeRootUrl,omitempty"`
 	// DexConfig contains portions of a dex config yaml
 	DexConfig string `json:"dexConfig,omitempty"`
 	// OIDCConfigRAW holds OIDC configuration as a raw string
@@ -91,6 +93,8 @@ type ArgoCDSettings struct {
 	PasswordPattern string `json:"passwordPattern,omitempty"`
 	// BinaryUrls contains the URLs for downloading argocd binaries
 	BinaryUrls map[string]string `json:"binaryUrls,omitempty"`
+	// InClusterEnabled indicates whether to allow in-cluster server address
+	InClusterEnabled bool `json:"inClusterEnabled"`
 }
 
 type GoogleAnalytics struct {
@@ -328,6 +332,8 @@ const (
 	settingsOIDCConfigKey = "oidc.config"
 	// statusBadgeEnabledKey holds the key which enables of disables status badge feature
 	statusBadgeEnabledKey = "statusbadge.enabled"
+	// statusBadgeRootUrlKey holds the key for the root badge URL override
+	statusBadgeRootUrlKey = "statusbadge.url"
 	// settingsWebhookGitHubSecret is the key for the GitHub shared webhook secret
 	settingsWebhookGitHubSecretKey = "webhook.github.secret"
 	// settingsWebhookGitLabSecret is the key for the GitLab shared webhook secret
@@ -388,6 +394,8 @@ const (
 	partOfArgoCDSelector = "app.kubernetes.io/part-of=argocd"
 	// settingsPasswordPatternKey is the key to configure user password regular expression
 	settingsPasswordPatternKey = "passwordPattern"
+	// inClusterEnabledKey is the key to configure whether to allow in-cluster server address
+	inClusterEnabledKey = "cluster.inClusterEnabled"
 )
 
 // SettingsManager holds config info for a new manager with which to access Kubernetes ConfigMaps.
@@ -1163,6 +1171,7 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi
 	settings.OIDCConfigRAW = argoCDCM.Data[settingsOIDCConfigKey]
 	settings.KustomizeBuildOptions = argoCDCM.Data[kustomizeBuildOptionsKey]
 	settings.StatusBadgeEnabled = argoCDCM.Data[statusBadgeEnabledKey] == "true"
+	settings.StatusBadgeRootUrl = argoCDCM.Data[statusBadgeRootUrlKey]
 	settings.AnonymousUserEnabled = argoCDCM.Data[anonymousUserEnabledKey] == "true"
 	settings.UiCssURL = argoCDCM.Data[settingUiCssURLKey]
 	settings.UiBannerContent = argoCDCM.Data[settingUiBannerContentKey]
@@ -1190,6 +1199,7 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi
 	if settings.PasswordPattern == "" {
 		settings.PasswordPattern = common.PasswordPatten
 	}
+	settings.InClusterEnabled = argoCDCM.Data[inClusterEnabledKey] != "false"
 }
 
 // validateExternalURL ensures the external URL that is set on the configmap is valid


Test patch:
diff --git a/cmd/argocd/commands/admin/app_test.go b/cmd/argocd/commands/admin/app_test.go
index bddedc4aa..3afea38b7 100644
--- a/cmd/argocd/commands/admin/app_test.go
+++ b/cmd/argocd/commands/admin/app_test.go
@@ -3,8 +3,6 @@ package admin
 import (
 	"testing"
 
-	"github.com/argoproj/argo-cd/v2/test"
-
 	clustermocks "github.com/argoproj/gitops-engine/pkg/cache/mocks"
 	"github.com/argoproj/gitops-engine/pkg/health"
 	"github.com/argoproj/gitops-engine/pkg/utils/kube"
@@ -21,8 +19,9 @@ import (
 	"github.com/argoproj/argo-cd/v2/controller/metrics"
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appfake "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
-	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
+	argocdclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
 	"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
+	"github.com/argoproj/argo-cd/v2/test"
 	"github.com/argoproj/argo-cd/v2/util/db"
 	"github.com/argoproj/argo-cd/v2/util/settings"
 )
@@ -90,7 +89,7 @@ func TestGetReconcileResults_Refresh(t *testing.T) {
 	clusterCache := clustermocks.ClusterCache{}
 	clusterCache.On("IsNamespaced", mock.Anything).Return(true, nil)
 	repoServerClient := mocks.RepoServerServiceClient{}
-	repoServerClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&apiclient.ManifestResponse{
+	repoServerClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&argocdclient.ManifestResponse{
 		Manifests: []string{test.DeploymentManifest},
 	}, nil)
 	repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
diff --git a/cmd/argocd/commands/cluster_test.go b/cmd/argocd/commands/cluster_test.go
index b0e978ab6..b1b25d030 100644
--- a/cmd/argocd/commands/cluster_test.go
+++ b/cmd/argocd/commands/cluster_test.go
@@ -3,11 +3,10 @@ package commands
 import (
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
-
-	"github.com/stretchr/testify/assert"
 )
 
 func Test_getQueryBySelector(t *testing.T) {
diff --git a/cmd/argocd/commands/headless/headless_test.go b/cmd/argocd/commands/initialize/cmd_test.go
similarity index 91%
rename from cmd/argocd/commands/headless/headless_test.go
rename to cmd/argocd/commands/initialize/cmd_test.go
index 15550714e..59f2efb55 100644
--- a/cmd/argocd/commands/headless/headless_test.go
+++ b/cmd/argocd/commands/initialize/cmd_test.go
@@ -1,4 +1,4 @@
-package headless
+package initialize
 
 import (
 	"testing"
@@ -26,7 +26,7 @@ func (f *StringFlag) Type() string {
 }
 
 func Test_FlagContextNotChanged(t *testing.T) {
-	res := retrieveContextIfChanged(&flag.Flag{
+	res := RetrieveContextIfChanged(&flag.Flag{
 		Name:                "",
 		Shorthand:           "",
 		Usage:               "",
@@ -44,7 +44,7 @@ func Test_FlagContextNotChanged(t *testing.T) {
 }
 
 func Test_FlagContextChanged(t *testing.T) {
-	res := retrieveContextIfChanged(&flag.Flag{
+	res := RetrieveContextIfChanged(&flag.Flag{
 		Name:                "",
 		Shorthand:           "",
 		Usage:               "",
@@ -62,7 +62,7 @@ func Test_FlagContextChanged(t *testing.T) {
 }
 
 func Test_FlagContextNil(t *testing.T) {
-	res := retrieveContextIfChanged(&flag.Flag{
+	res := RetrieveContextIfChanged(&flag.Flag{
 		Name:                "",
 		Shorthand:           "",
 		Usage:               "",
diff --git a/cmd/argocd/commands/logout_test.go b/cmd/argocd/commands/logout_test.go
index 6fa17aace..1cf83e5ed 100644
--- a/cmd/argocd/commands/logout_test.go
+++ b/cmd/argocd/commands/logout_test.go
@@ -5,10 +5,9 @@ import (
 	"os"
 	"testing"
 
-	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
-
 	"github.com/stretchr/testify/assert"
 
+	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	"github.com/argoproj/argo-cd/v2/util/localconfig"
 )
 
@@ -23,7 +22,7 @@ func TestLogout(t *testing.T) {
 	assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
 	assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
 
-	command := NewLogoutCommand(&apiclient.ClientOptions{ConfigPath: testConfigFilePath})
+	command := NewLogoutCommand(&argocdclient.ClientOptions{ConfigPath: testConfigFilePath})
 	command.Run(nil, []string{"localhost:8080"})
 
 	localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
diff --git a/controller/clusterinfoupdater_test.go b/controller/clusterinfoupdater_test.go
index 993e63b8d..9b99f8b03 100644
--- a/controller/clusterinfoupdater_test.go
+++ b/controller/clusterinfoupdater_test.go
@@ -6,6 +6,11 @@ import (
 	"testing"
 	"time"
 
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/argoproj/argo-cd/v2/common"
+
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appsfake "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
 	appinformers "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions/application/v1alpha1"
@@ -37,7 +42,30 @@ func TestClusterSecretUpdater(t *testing.T) {
 		{&now, fmt.Errorf("sync failed"), v1alpha1.ConnectionStatusFailed},
 	}
 
-	kubeclientset := fake.NewSimpleClientset()
+	emptyArgoCDConfigMap := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDConfigMapName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string]string{},
+	}
+	argoCDSecret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDSecretName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string][]byte{
+			"admin.password":   nil,
+			"server.secretkey": nil,
+		},
+	}
+	kubeclientset := fake.NewSimpleClientset(emptyArgoCDConfigMap, argoCDSecret)
 	appclientset := appsfake.NewSimpleClientset()
 	appInformer := appinformers.NewApplicationInformer(appclientset, "", time.Minute, cache.Indexers{})
 	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
diff --git a/docs/developer-guide/test-e2e.md b/docs/developer-guide/test-e2e.md
index bb7fc85be..3d46fd55f 100644
--- a/docs/developer-guide/test-e2e.md
+++ b/docs/developer-guide/test-e2e.md
@@ -11,7 +11,7 @@ Git repository via file url: `file:///tmp/argocd-e2e***`.
 1. Start the e2e version `make start-e2e` 
 1. Run the tests: `make test-e2e`
 
-You can observe the tests by using the UI [http://localhost:4000/applications](http://localhost:4000/applications).
+You can observe the tests by using the UI [http://localhost:8080/applications](http://localhost:8080/applications) with username `"admin"` and password `"password"`.
 
 ## Configuration of E2E Tests execution
 
diff --git a/resource_customizations/apps.openshift.io/DeploymentConfig/health_test.yaml b/resource_customizations/apps.openshift.io/DeploymentConfig/health_test.yaml
index 1df01ee61..46ebdb94c 100644
--- a/resource_customizations/apps.openshift.io/DeploymentConfig/health_test.yaml
+++ b/resource_customizations/apps.openshift.io/DeploymentConfig/health_test.yaml
@@ -10,4 +10,8 @@ tests:
 - healthStatus:
     status: Healthy
     message: "replication controller successfully rolled out"
-  inputPath: testdata/healthy.yaml
\ No newline at end of file
+  inputPath: testdata/healthy.yaml
+- healthStatus:
+    status: Healthy
+    message: "replication controller successfully rolled out"
+  inputPath: testdata/healthy_zero_replicas.yaml
\ No newline at end of file
diff --git a/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/degraded.yaml b/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/degraded.yaml
index 32e89d872..31379fa2c 100644
--- a/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/degraded.yaml
+++ b/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/degraded.yaml
@@ -133,10 +133,10 @@ spec:
 status:
   latestVersion: 1
   observedGeneration: 1
-  replicas: 0
-  updatedReplicas: 0
+  replicas: 10
+  updatedReplicas: 10
   availableReplicas: 0
-  unavailableReplicas: 0
+  unavailableReplicas: 10
   details:
     message: config change
     causes:
diff --git a/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/healthy_zero_replicas.yaml b/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/healthy_zero_replicas.yaml
new file mode 100644
index 000000000..c0837a3f5
--- /dev/null
+++ b/resource_customizations/apps.openshift.io/DeploymentConfig/testdata/healthy_zero_replicas.yaml
@@ -0,0 +1,68 @@
+kind: DeploymentConfig
+apiVersion: apps.openshift.io/v1
+metadata:
+  name: example
+  namespace: default
+spec:
+  strategy:
+    type: Rolling
+    rollingParams:
+      updatePeriodSeconds: 1
+      intervalSeconds: 1
+      timeoutSeconds: 600
+      maxUnavailable: 25%
+      maxSurge: 25%
+    resources: {}
+    activeDeadlineSeconds: 21600
+  triggers:
+    - type: ConfigChange
+  replicas: 3
+  revisionHistoryLimit: 10
+  test: false
+  selector:
+    app: httpd
+  template:
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: httpd
+    spec:
+      containers:
+        - name: httpd
+          image: >-
+            image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest
+          ports:
+            - containerPort: 8080
+              protocol: TCP
+          resources: {}
+          terminationMessagePath: /dev/termination-log
+          terminationMessagePolicy: File
+          imagePullPolicy: Always
+      restartPolicy: Always
+      terminationGracePeriodSeconds: 30
+      dnsPolicy: ClusterFirst
+      securityContext: {}
+      schedulerName: default-scheduler
+status:
+  availableReplicas: 0
+  conditions:
+    - lastTransitionTime: '2022-02-02T12:22:22Z'
+      lastUpdateTime: '2022-02-02T12:23:53Z'
+      message: replication controller "jenkins-1" successfully rolled out
+      reason: NewReplicationControllerAvailable
+      status: 'True'
+      type: Progressing
+    - lastTransitionTime: '2022-02-02T14:11:11Z'
+      lastUpdateTime: '2022-02-02T14:11:11Z'
+      message: Deployment config does not have minimum availability.
+      status: 'False'
+      type: Available
+  details:
+    causes:
+      - type: ConfigChange
+    message: config change
+  latestVersion: 1
+  observedGeneration: 5
+  replicas: 0
+  unavailableReplicas: 0
+  updatedReplicas: 0
\ No newline at end of file
diff --git a/test/e2e/accounts_test.go b/test/e2e/accounts_test.go
index b5f9a4160..285f7ed09 100644
--- a/test/e2e/accounts_test.go
+++ b/test/e2e/accounts_test.go
@@ -4,14 +4,14 @@ import (
 	"context"
 	"testing"
 
-	"github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
-
 	"github.com/argoproj/pkg/errors"
+	"github.com/spf13/cobra"
 	"github.com/stretchr/testify/assert"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
-	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
+	"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
 	"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
 	. "github.com/argoproj/argo-cd/v2/test/e2e/fixture"
 	accountFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/account"
@@ -63,7 +63,7 @@ test   true     login, apiKey`, output)
 
 	clientOpts := ArgoCDClientset.ClientOptions()
 	clientOpts.AuthToken = token
-	testAccountClientset := argocdclient.NewClientOrDie(&clientOpts)
+	testAccountClientset := headless.NewClientOrDie(&clientOpts, &cobra.Command{})
 
 	closer, client := testAccountClientset.NewSessionClientOrDie()
 	defer io.Close(closer)
diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go
index 6b2c237eb..f7f742db3 100644
--- a/test/e2e/app_management_test.go
+++ b/test/e2e/app_management_test.go
@@ -21,7 +21,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	v1 "k8s.io/api/core/v1"
-	networkingv1beta "k8s.io/api/networking/v1beta1"
+	networkingv1 "k8s.io/api/networking/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -650,7 +650,7 @@ func TestKnownTypesInCRDDiffing(t *testing.T) {
 		When().
 		And(func() {
 			dummyResIf := DynamicClientset.Resource(dummiesGVR).Namespace(DeploymentNamespace())
-			patchData := []byte(`{"spec":{"requests": {"cpu": "2"}}}`)
+			patchData := []byte(`{"spec":{"cpu": "2"}}`)
 			FailOnErr(dummyResIf.Patch(context.Background(), "dummy-crd-instance", types.MergePatchType, patchData, metav1.PatchOptions{}))
 		}).Refresh(RefreshTypeNormal).
 		Then().
@@ -660,7 +660,7 @@ func TestKnownTypesInCRDDiffing(t *testing.T) {
 			SetResourceOverrides(map[string]ResourceOverride{
 				"argoproj.io/Dummy": {
 					KnownTypeFields: []KnownTypeField{{
-						Field: "spec.requests",
+						Field: "spec",
 						Type:  "core/v1/ResourceList",
 					}},
 				},
@@ -1278,23 +1278,27 @@ func TestOrphanedResource(t *testing.T) {
 func TestNotPermittedResources(t *testing.T) {
 	ctx := Given(t)
 
-	ingress := &networkingv1beta.Ingress{
+	pathType := networkingv1.PathTypePrefix
+	ingress := &networkingv1.Ingress{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: "sample-ingress",
 			Labels: map[string]string{
 				common.LabelKeyAppInstance: ctx.GetName(),
 			},
 		},
-		Spec: networkingv1beta.IngressSpec{
-			Rules: []networkingv1beta.IngressRule{{
-				IngressRuleValue: networkingv1beta.IngressRuleValue{
-					HTTP: &networkingv1beta.HTTPIngressRuleValue{
-						Paths: []networkingv1beta.HTTPIngressPath{{
+		Spec: networkingv1.IngressSpec{
+			Rules: []networkingv1.IngressRule{{
+				IngressRuleValue: networkingv1.IngressRuleValue{
+					HTTP: &networkingv1.HTTPIngressRuleValue{
+						Paths: []networkingv1.HTTPIngressPath{{
 							Path: "/",
-							Backend: networkingv1beta.IngressBackend{
-								ServiceName: "guestbook-ui",
-								ServicePort: intstr.IntOrString{Type: intstr.Int, IntVal: 80},
+							Backend: networkingv1.IngressBackend{
+								Service: &networkingv1.IngressServiceBackend{
+									Name: "guestbook-ui",
+									Port: networkingv1.ServiceBackendPort{Number: 80},
+								},
 							},
+							PathType: &pathType,
 						}},
 					},
 				},
@@ -1303,7 +1307,7 @@ func TestNotPermittedResources(t *testing.T) {
 	}
 	defer func() {
 		log.Infof("Ingress 'sample-ingress' deleted from %s", ArgoCDNamespace)
-		CheckError(KubeClientset.NetworkingV1beta1().Ingresses(ArgoCDNamespace).Delete(context.Background(), "sample-ingress", metav1.DeleteOptions{}))
+		CheckError(KubeClientset.NetworkingV1().Ingresses(ArgoCDNamespace).Delete(context.Background(), "sample-ingress", metav1.DeleteOptions{}))
 	}()
 
 	svc := &v1.Service{
@@ -1331,7 +1335,7 @@ func TestNotPermittedResources(t *testing.T) {
 			{Group: "", Kind: "Service"},
 		}}).
 		And(func() {
-			FailOnErr(KubeClientset.NetworkingV1beta1().Ingresses(ArgoCDNamespace).Create(context.Background(), ingress, metav1.CreateOptions{}))
+			FailOnErr(KubeClientset.NetworkingV1().Ingresses(ArgoCDNamespace).Create(context.Background(), ingress, metav1.CreateOptions{}))
 			FailOnErr(KubeClientset.CoreV1().Services(DeploymentNamespace()).Create(context.Background(), svc, metav1.CreateOptions{}))
 		}).
 		Path(guestbookPath).
@@ -1357,7 +1361,7 @@ func TestNotPermittedResources(t *testing.T) {
 		Expect(DoesNotExist())
 
 	// Make sure prohibited resources are not deleted during application deletion
-	FailOnErr(KubeClientset.NetworkingV1beta1().Ingresses(ArgoCDNamespace).Get(context.Background(), "sample-ingress", metav1.GetOptions{}))
+	FailOnErr(KubeClientset.NetworkingV1().Ingresses(ArgoCDNamespace).Get(context.Background(), "sample-ingress", metav1.GetOptions{}))
 	FailOnErr(KubeClientset.CoreV1().Services(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{}))
 }
 
diff --git a/test/e2e/fixture/fixture.go b/test/e2e/fixture/fixture.go
index d1e1ebbd0..9589e7c13 100644
--- a/test/e2e/fixture/fixture.go
+++ b/test/e2e/fixture/fixture.go
@@ -26,7 +26,7 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 
 	"github.com/argoproj/argo-cd/v2/common"
-	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
 	sessionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
@@ -72,7 +72,7 @@ var (
 	KubeConfig          *rest.Config
 	DynamicClientset    dynamic.Interface
 	AppClientset        appclientset.Interface
-	ArgoCDClientset     argocdclient.Client
+	ArgoCDClientset     apiclient.Client
 	adminUsername       string
 	AdminPassword       string
 	apiServerAddress    string
@@ -156,14 +156,14 @@ func init() {
 	DynamicClientset = dynamic.NewForConfigOrDie(config)
 	KubeConfig = config
 
-	apiServerAddress = GetEnvWithDefault(argocdclient.EnvArgoCDServer, defaultApiServer)
+	apiServerAddress = GetEnvWithDefault(apiclient.EnvArgoCDServer, defaultApiServer)
 	adminUsername = GetEnvWithDefault(EnvAdminUsername, defaultAdminUsername)
 	AdminPassword = GetEnvWithDefault(EnvAdminPassword, defaultAdminPassword)
 
 	tlsTestResult, err := grpcutil.TestTLS(apiServerAddress)
 	CheckError(err)
 
-	ArgoCDClientset, err = argocdclient.NewClient(&argocdclient.ClientOptions{Insecure: true, ServerAddr: apiServerAddress, PlainText: !tlsTestResult.TLS})
+	ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{Insecure: true, ServerAddr: apiServerAddress, PlainText: !tlsTestResult.TLS})
 	CheckError(err)
 
 	plainText = !tlsTestResult.TLS
@@ -203,7 +203,7 @@ func loginAs(username, password string) {
 	CheckError(err)
 	token = sessionResponse.Token
 
-	ArgoCDClientset, err = argocdclient.NewClient(&argocdclient.ClientOptions{
+	ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{
 		Insecure:   true,
 		ServerAddr: apiServerAddress,
 		AuthToken:  token,
diff --git a/test/e2e/testdata/crd-creation/crd-instances.yaml b/test/e2e/testdata/crd-creation/crd-instances.yaml
index 29c01a401..59ec50a92 100644
--- a/test/e2e/testdata/crd-creation/crd-instances.yaml
+++ b/test/e2e/testdata/crd-creation/crd-instances.yaml
@@ -4,9 +4,8 @@ kind: Dummy
 metadata:
   name: dummy-crd-instance
 spec:
-  requests:
-    cpu: 2000m
-    memory: 32Mi
+  cpu: 2000m
+  memory: 32Mi
 ---
 apiVersion: argoproj.io/v1alpha1
 kind: Dummy
diff --git a/test/e2e/testdata/crd-creation/crd.yaml b/test/e2e/testdata/crd-creation/crd.yaml
index 53b1943ea..2216607cb 100644
--- a/test/e2e/testdata/crd-creation/crd.yaml
+++ b/test/e2e/testdata/crd-creation/crd.yaml
@@ -1,24 +1,69 @@
 ---
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: dummies.argoproj.io
 spec:
+  conversion:
+    strategy: None
   group: argoproj.io
-  version: v1alpha1
-  scope: Namespaced
   names:
     kind: Dummy
+    listKind: DummyList
     plural: dummies
+    singular: dummy
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            properties:
+              cpu:
+                type: string
+              memory:
+                type: string
 ---
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: clusterdummies.argoproj.io
 spec:
+  conversion:
+    strategy: None
   group: argoproj.io
-  version: v1alpha1
-  scope: Cluster
   names:
     kind: ClusterDummy
+    listKind: ClusterDummyList
     plural: clusterdummies
+    singular: clusterdummy
+  scope: Cluster
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
diff --git a/test/e2e/testdata/crd-subresource/crd.yaml b/test/e2e/testdata/crd-subresource/crd.yaml
index 4d1e37c43..6c59c3e2d 100644
--- a/test/e2e/testdata/crd-subresource/crd.yaml
+++ b/test/e2e/testdata/crd-subresource/crd.yaml
@@ -1,25 +1,88 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: statussubresources.argoproj.io
 spec:
+  conversion:
+    strategy: None
   group: argoproj.io
-  version: v1alpha1
-  scope: Namespaced
   names:
     kind: StatusSubResource
+    listKind: StatusSubResourceList
     plural: statussubresources
-  subresources:
-   status: {}
+    singular: statussubresource
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    subresources:
+      status: {}
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
+            properties:
+              foo:
+                type: string
+          status:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
+            properties:
+              bar:
+                type: string
+
 ---
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: nonstatussubresources.argoproj.io
 spec:
+  conversion:
+    strategy: None
   group: argoproj.io
-  version: v1alpha1
-  scope: Namespaced
   names:
     kind: NonStatusSubResource
+    listKind: NonStatusSubResourceList
     plural: nonstatussubresources
+    singular: nonstatussubresource
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
+            properties:
+              foo:
+                type: string
+          status:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
+            properties:
+              bar:
+                type: string
diff --git a/test/e2e/testdata/duplicated-resources/duplicates-cluster.yaml b/test/e2e/testdata/duplicated-resources/duplicates-cluster.yaml
index a2e6e9527..acd0f4025 100644
--- a/test/e2e/testdata/duplicated-resources/duplicates-cluster.yaml
+++ b/test/e2e/testdata/duplicated-resources/duplicates-cluster.yaml
@@ -1,16 +1,36 @@
 ---
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: clusterdummies.argoproj.io
 spec:
+  conversion:
+    strategy: None
   group: argoproj.io
-  version: v1alpha1
-  scope: Cluster
   names:
     kind: ClusterDummy
+    listKind: ClusterDummyList
     plural: clusterdummies
-
+    singular: clusterdummy
+  scope: Cluster
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
 
 ---
 apiVersion: argoproj.io/v1alpha1
diff --git a/test/e2e/testdata/duplicated-resources/duplicates-namespaced.yaml b/test/e2e/testdata/duplicated-resources/duplicates-namespaced.yaml
index 3f19da751..58458eadb 100644
--- a/test/e2e/testdata/duplicated-resources/duplicates-namespaced.yaml
+++ b/test/e2e/testdata/duplicated-resources/duplicates-namespaced.yaml
@@ -1,15 +1,36 @@
 ---
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: dummies.argoproj.io
 spec:
+  conversion:
+    strategy: None
   group: argoproj.io
-  version: v1alpha1
-  scope: Namespaced
   names:
     kind: Dummy
+    listKind: DummyList
     plural: dummies
+    singular: dummy
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
 
 ---
 apiVersion: argoproj.io/v1alpha1
diff --git a/test/e2e/testdata/helm-crd/templates/crd.yaml b/test/e2e/testdata/helm-crd/templates/crd.yaml
index 9e02aaffb..e5b59cc44 100644
--- a/test/e2e/testdata/helm-crd/templates/crd.yaml
+++ b/test/e2e/testdata/helm-crd/templates/crd.yaml
@@ -1,14 +1,34 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: crontabs.stable.example.com
 spec:
+  conversion:
+    strategy: None
   group: stable.example.com
-  version: v1
-  scope: Namespaced
   names:
-    plural: crontabs
-    singular: crontab
     kind: CronTab
+    listKind: CronTabList
+    plural: crontabs
     shortNames:
-      - ct
\ No newline at end of file
+    - ct
+    singular: crontab
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
diff --git a/test/e2e/testdata/helm3-crd/crds/crd.yaml b/test/e2e/testdata/helm3-crd/crds/crd.yaml
index 9e02aaffb..e5b59cc44 100644
--- a/test/e2e/testdata/helm3-crd/crds/crd.yaml
+++ b/test/e2e/testdata/helm3-crd/crds/crd.yaml
@@ -1,14 +1,34 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: crontabs.stable.example.com
 spec:
+  conversion:
+    strategy: None
   group: stable.example.com
-  version: v1
-  scope: Namespaced
   names:
-    plural: crontabs
-    singular: crontab
     kind: CronTab
+    listKind: CronTabList
+    plural: crontabs
     shortNames:
-      - ct
\ No newline at end of file
+    - ct
+    singular: crontab
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          apiVersion:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            type: object
+            x-kubernetes-map-type: atomic
+            x-kubernetes-preserve-unknown-fields: true
diff --git a/util/argo/normalizers/diff_normalizer_test.go b/util/argo/normalizers/diff_normalizer_test.go
index 3cd63966e..f872ca367 100644
--- a/util/argo/normalizers/diff_normalizer_test.go
+++ b/util/argo/normalizers/diff_normalizer_test.go
@@ -78,22 +78,34 @@ func TestNormalizeMatchedResourceOverrides(t *testing.T) {
 }
 
 const testCRDYAML = `
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: certificates.cert-manager.io
 spec:
+  conversion:
+    strategy: None
   group: cert-manager.io
   names:
     kind: Certificate
     listKind: CertificateList
     plural: certificates
     shortNames:
-      - cert
-      - certs
+    - cert
+    - certs
     singular: certificate
   scope: Namespaced
-  version: v1alpha1`
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          json:
+            x-kubernetes-preserve-unknown-fields: true`
 
 func TestNormalizeMissingJsonPointer(t *testing.T) {
 	normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{
diff --git a/util/db/cluster_norace_test.go b/util/db/cluster_norace_test.go
index 655011bd1..10f50be2d 100644
--- a/util/db/cluster_norace_test.go
+++ b/util/db/cluster_norace_test.go
@@ -7,6 +7,11 @@ import (
 	"context"
 	"testing"
 
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/argoproj/argo-cd/v2/common"
+
 	"github.com/stretchr/testify/assert"
 	"k8s.io/client-go/kubernetes/fake"
 
@@ -19,8 +24,30 @@ func TestWatchClusters_CreateRemoveCluster(t *testing.T) {
 	// !race:
 	// Intermittent failure when running TestWatchClusters_LocalClusterModifications with -race, likely due to race condition
 	// https://github.com/argoproj/argo-cd/issues/4755
-
-	kubeclientset := fake.NewSimpleClientset()
+	emptyArgoCDConfigMap := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDConfigMapName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string]string{},
+	}
+	argoCDSecret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDSecretName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string][]byte{
+			"admin.password":   nil,
+			"server.secretkey": nil,
+		},
+	}
+	kubeclientset := fake.NewSimpleClientset(emptyArgoCDConfigMap, argoCDSecret)
 	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
 	db := NewDB(fakeNamespace, settingsManager, kubeclientset)
 	runWatchTest(t, db, []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster){
@@ -53,8 +80,30 @@ func TestWatchClusters_LocalClusterModifications(t *testing.T) {
 	// !race:
 	// Intermittent failure when running TestWatchClusters_LocalClusterModifications with -race, likely due to race condition
 	// https://github.com/argoproj/argo-cd/issues/4755
-
-	kubeclientset := fake.NewSimpleClientset()
+	emptyArgoCDConfigMap := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDConfigMapName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string]string{},
+	}
+	argoCDSecret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDSecretName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string][]byte{
+			"admin.password":   nil,
+			"server.secretkey": nil,
+		},
+	}
+	kubeclientset := fake.NewSimpleClientset(emptyArgoCDConfigMap, argoCDSecret)
 	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
 	db := NewDB(fakeNamespace, settingsManager, kubeclientset)
 	runWatchTest(t, db, []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster){
diff --git a/util/db/cluster_test.go b/util/db/cluster_test.go
index 6a483ce72..c3b273b4f 100644
--- a/util/db/cluster_test.go
+++ b/util/db/cluster_test.go
@@ -169,6 +169,40 @@ func TestDeleteUnknownCluster(t *testing.T) {
 	assert.EqualError(t, db.DeleteCluster(context.Background(), "http://unknown"), `rpc error: code = NotFound desc = cluster "http://unknown" not found`)
 }
 
+func TestRejectCreationForInClusterWhenDisabled(t *testing.T) {
+	argoCDConfigMapWithInClusterServerAddressDisabled := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDConfigMapName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string]string{"cluster.inClusterEnabled": "false"},
+	}
+	argoCDSecret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDSecretName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string][]byte{
+			"admin.password":   nil,
+			"server.secretkey": nil,
+		},
+	}
+	kubeclientset := fake.NewSimpleClientset(argoCDConfigMapWithInClusterServerAddressDisabled, argoCDSecret)
+	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
+	db := NewDB(fakeNamespace, settingsManager, kubeclientset)
+	_, err := db.CreateCluster(context.Background(), &appv1.Cluster{
+		Server: appv1.KubernetesInternalAPIServerAddr,
+		Name:   "incluster-name",
+	})
+	assert.Error(t, err)
+}
+
 func runWatchTest(t *testing.T, db ArgoDB, actions []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster)) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
@@ -212,7 +246,40 @@ func runWatchTest(t *testing.T, db ArgoDB, actions []func(old *v1alpha1.Cluster,
 }
 
 func TestListClusters(t *testing.T) {
-	validSecret1 := &v1.Secret{
+	emptyArgoCDConfigMap := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDConfigMapName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string]string{},
+	}
+	argoCDConfigMapWithInClusterServerAddressDisabled := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDConfigMapName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string]string{"cluster.inClusterEnabled": "false"},
+	}
+	argoCDSecret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.ArgoCDSecretName,
+			Namespace: fakeNamespace,
+			Labels: map[string]string{
+				"app.kubernetes.io/part-of": "argocd",
+			},
+		},
+		Data: map[string][]byte{
+			"admin.password":   nil,
+			"server.secretkey": nil,
+		},
+	}
+	secretForServerWithInClusterAddr := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "mycluster1",
 			Namespace: fakeNamespace,
@@ -226,7 +293,7 @@ func TestListClusters(t *testing.T) {
 		},
 	}
 
-	validSecret2 := &v1.Secret{
+	secretForServerWithExternalClusterAddr := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "mycluster2",
 			Namespace: fakeNamespace,
@@ -253,7 +320,7 @@ func TestListClusters(t *testing.T) {
 	}
 
 	t.Run("Valid clusters", func(t *testing.T) {
-		kubeclientset := fake.NewSimpleClientset(validSecret1, validSecret2)
+		kubeclientset := fake.NewSimpleClientset(secretForServerWithInClusterAddr, secretForServerWithExternalClusterAddr, emptyArgoCDConfigMap, argoCDSecret)
 		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
 		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
 
@@ -263,7 +330,7 @@ func TestListClusters(t *testing.T) {
 	})
 
 	t.Run("Cluster list with invalid cluster", func(t *testing.T) {
-		kubeclientset := fake.NewSimpleClientset(validSecret1, validSecret2, invalidSecret)
+		kubeclientset := fake.NewSimpleClientset(secretForServerWithInClusterAddr, secretForServerWithExternalClusterAddr, invalidSecret, emptyArgoCDConfigMap, argoCDSecret)
 		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
 		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
 
@@ -273,7 +340,7 @@ func TestListClusters(t *testing.T) {
 	})
 
 	t.Run("Implicit in-cluster secret", func(t *testing.T) {
-		kubeclientset := fake.NewSimpleClientset(validSecret2)
+		kubeclientset := fake.NewSimpleClientset(secretForServerWithExternalClusterAddr, emptyArgoCDConfigMap, argoCDSecret)
 		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
 		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
 
@@ -282,4 +349,24 @@ func TestListClusters(t *testing.T) {
 		// ListClusters() should have added an implicit in-cluster secret to the list
 		assert.Len(t, clusters.Items, 2)
 	})
+
+	t.Run("ListClusters() should not add the cluster with in-cluster server address since in-cluster is disabled", func(t *testing.T) {
+		kubeclientset := fake.NewSimpleClientset(secretForServerWithInClusterAddr, argoCDConfigMapWithInClusterServerAddressDisabled, argoCDSecret)
+		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
+		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
+
+		clusters, err := db.ListClusters(context.TODO())
+		require.NoError(t, err)
+		assert.Len(t, clusters.Items, 0)
+	})
+
+	t.Run("ListClusters() should add this cluster since it does not contain in-cluster server address even though in-cluster is disabled", func(t *testing.T) {
+		kubeclientset := fake.NewSimpleClientset(secretForServerWithExternalClusterAddr, argoCDConfigMapWithInClusterServerAddressDisabled, argoCDSecret)
+		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
+		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
+
+		clusters, err := db.ListClusters(context.TODO())
+		require.NoError(t, err)
+		assert.Len(t, clusters.Items, 1)
+	})
 }
diff --git a/util/env/env_test.go b/util/env/env_test.go
index 1db9ebe0f..10145dca6 100644
--- a/util/env/env_test.go
+++ b/util/env/env_test.go
@@ -1,7 +1,9 @@
 package env
 
 import (
+	"fmt"
 	"io"
+	"math"
 	"os"
 	"testing"
 	"time"
@@ -62,6 +64,84 @@ func TestParseNumFromEnv_OutOfRangeValueSet(t *testing.T) {
 	assert.Equal(t, 10, num)
 }
 
+func TestParseFloatFromEnv(t *testing.T) {
+	t.Run("Env not set", func(t *testing.T) {
+		closer := setEnv(t, "test", "")
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, math.MaxFloat32)
+		assert.Equal(t, float32(1.0), f)
+	})
+	t.Run("Parse valid float", func(t *testing.T) {
+		closer := setEnv(t, "test", "2.5")
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, math.MaxFloat32)
+		assert.Equal(t, float32(2.5), f)
+	})
+	t.Run("Parse valid integer as float", func(t *testing.T) {
+		closer := setEnv(t, "test", "2")
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, math.MaxFloat32)
+		assert.Equal(t, float32(2.0), f)
+	})
+	t.Run("Parse invalid value", func(t *testing.T) {
+		closer := setEnv(t, "test", "foo")
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, math.MaxFloat32)
+		assert.Equal(t, float32(1.0), f)
+	})
+	t.Run("Float lesser than allowed", func(t *testing.T) {
+		closer := setEnv(t, "test", "-2.0")
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, math.MaxFloat32)
+		assert.Equal(t, float32(1.0), f)
+	})
+	t.Run("Float greater than allowed", func(t *testing.T) {
+		closer := setEnv(t, "test", "5.0")
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, 4)
+		assert.Equal(t, float32(1.0), f)
+	})
+	t.Run("Check float overflow returning default value", func(t *testing.T) {
+		closer := setEnv(t, "test", fmt.Sprintf("%f", math.MaxFloat32*2))
+		defer util.Close(closer)
+		f := ParseFloatFromEnv("test", 1, 0, math.MaxFloat32)
+		assert.Equal(t, float32(1.0), f)
+	})
+}
+
+func TestParseInt64FromEnv(t *testing.T) {
+	t.Run("Env not set", func(t *testing.T) {
+		closer := setEnv(t, "test", "")
+		defer util.Close(closer)
+		i := ParseInt64FromEnv("test", 1, 0, math.MaxInt64)
+		assert.Equal(t, int64(1), i)
+	})
+	t.Run("Parse valid int64", func(t *testing.T) {
+		closer := setEnv(t, "test", "3")
+		defer util.Close(closer)
+		i := ParseInt64FromEnv("test", 1, 0, math.MaxInt64)
+		assert.Equal(t, int64(3), i)
+	})
+	t.Run("Parse invalid value", func(t *testing.T) {
+		closer := setEnv(t, "test", "foo")
+		defer util.Close(closer)
+		i := ParseInt64FromEnv("test", 1, 0, math.MaxInt64)
+		assert.Equal(t, int64(1), i)
+	})
+	t.Run("Int64 lesser than allowed", func(t *testing.T) {
+		closer := setEnv(t, "test", "-2")
+		defer util.Close(closer)
+		i := ParseInt64FromEnv("test", 1, 0, math.MaxInt64)
+		assert.Equal(t, int64(1), i)
+	})
+	t.Run("Int64 greater than allowed", func(t *testing.T) {
+		closer := setEnv(t, "test", "5")
+		defer util.Close(closer)
+		i := ParseInt64FromEnv("test", 1, 0, 4)
+		assert.Equal(t, int64(1), i)
+	})
+}
+
 func TestParseDurationFromEnv(t *testing.T) {
 	testKey := "key"
 	defaultVal := 2 * time.Second
diff --git a/util/settings/settings_test.go b/util/settings/settings_test.go
index 26b8aa4fb..7701a27b7 100644
--- a/util/settings/settings_test.go
+++ b/util/settings/settings_test.go
@@ -135,6 +135,54 @@ func TestGetConfigManagementPlugins(t *testing.T) {
 	}}, plugins)
 }
 
+func TestInClusterServerAddressEnabled(t *testing.T) {
+	_, settingsManager := fixtures(map[string]string{
+		"cluster.inClusterEnabled": "true",
+	})
+	argoCDCM, err := settingsManager.getConfigMap()
+	assert.NoError(t, err)
+	assert.Equal(t, true, argoCDCM.Data[inClusterEnabledKey] == "true")
+
+	_, settingsManager = fixtures(map[string]string{
+		"cluster.inClusterEnabled": "false",
+	})
+	argoCDCM, err = settingsManager.getConfigMap()
+	assert.NoError(t, err)
+	assert.Equal(t, false, argoCDCM.Data[inClusterEnabledKey] == "true")
+}
+
+func TestInClusterServerAddressEnabledByDefault(t *testing.T) {
+	kubeClient := fake.NewSimpleClientset(
+		&v1.ConfigMap{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      common.ArgoCDConfigMapName,
+				Namespace: "default",
+				Labels: map[string]string{
+					"app.kubernetes.io/part-of": "argocd",
+				},
+			},
+			Data: map[string]string{},
+		},
+		&v1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      common.ArgoCDSecretName,
+				Namespace: "default",
+				Labels: map[string]string{
+					"app.kubernetes.io/part-of": "argocd",
+				},
+			},
+			Data: map[string][]byte{
+				"admin.password":   nil,
+				"server.secretkey": nil,
+			},
+		},
+	)
+	settingsManager := NewSettingsManager(context.Background(), kubeClient, "default")
+	settings, err := settingsManager.GetSettings()
+	assert.NoError(t, err)
+	assert.Equal(t, true, settings.InClusterEnabled)
+}
+
 func TestGetAppInstanceLabelKey(t *testing.T) {
 	_, settingsManager := fixtures(map[string]string{
 		"application.instanceLabelKey": "testLabel",