Repository: Azure/ARO-RP
Pull request #3558 (2024-05-02 17:25:32)

`make ci` support for golang (RP) components
### Which issue this PR addresses:

<!--
Please include a link to the ADO work item as well as any GitHub issues.

Usage: `Fixes #<GitHub issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes [ARO-5087](https://issues.redhat.com/browse/ARO-5087). Special shout-out to #tony-schndr for his amazing work in getting the massive initial hurdles out of the way!

### What this PR does / why we need it:

<!--
Include a brief summary of what the PR is intended to accomplish and how the PR
does it. (2-3 sentences)
-->

* Adds a Makefile target `make ci-rp` that issues a `podman build` command to consolidate 100% of required validation steps into a container build process (read: in the containerfile) so that it runs anywhere (local, pr, and on trunk commits in CI).
* Creates a clear line of logic and removes complex inter-dependencies during the build process. I hope I have clarified the exact build requirements by only moving commands in one direction flow: Makefile >> docker build >> shell commands. There are no longer complex chains of logic to parse out (e.g. "what env vars do I need to set for this command to work?" "what does shell script X do, and what are it's dependencies?" etc. I hope the "magic" of our build process is now demystified in a single place that can be read clearly from top to bottom and used as a source of truth (the Dockerfile). As an added benefit, by moving away from `make`, we are now allowed to disassociate from `git`, meaning we can actually use the docker build cache (alternative being that we `COPY .git .git`, which will always invalidate the cache on each build)
* As with `ci-portal`, this change accommodates local podman build caching to improve performance so it only rebuilds when specific changes are present (e.g. changes in `/python` or `/docs` will not invalidate the cache). I would generally recommend taking advantage of this by including `NO_CACHE=false` in your `env` file. At the same time, it's important to build without caching every once in a while to be sure your changes work with the most recent versions of things (e.g. linux packages pulled in the image with dnf).


### Test plan for issue:

<!--
How did you test that this PR works?

- Are there unit tests?
- Are there integration/e2e tests?
- If it is not possible to write automated tests, explain why and document how
  to manually test and verify the feature.
-->

Everyone, everywhere, all the time, should be able to run `make ci-rp` and it "just works". I need feedback from people globally and on various OS (I have tested on Fedora 39, Podman 4.9.4).

### Is there any documentation that needs to be updated for this PR?

<!--
- If yes and the docs are in GitHub, include doc updates in the PR.
- If yes and the docs are not in GitHub (i.e. ADO wiki), include a link to the
  docs.
- If no, explain why (e.g. "tech debt cleanup, N/A").
-->

Yes, this affects local development flows, **in particular for Mac users**:

1. We re-discovered that `golangci-lint` is a HOG, and needs something like 4GB of memory to execute successfully. Ensuring that `podman machine` is properly configured with enough memory is critical here, and there are documentation updates for how to do that.
2. MacOS, with a case-insensitive filesystem, may build `vendor` differently locally in a way not compatible with Linux. We added a Makefile target to fix this automatically in the meantime, but I think the removal of `vendor` is the long-term solution to consistent builds everywhere.

### How do you know this will function as expected in production? 

<!--
- Does adequate telemetry, monitoring and documentation exist to effectively operate your change?
- Have failure modes been identified and mitigated? 
-->

Currently not a production change - this will be a parallel CI/CD effort that for now does not impact prod. Eventually, these container builds will be used to generate consistent artifacts and create a chain of custody into prod - improving our consistency and resiliency during release. All of this work will be reviewable in future PRs, and are not included here.


SudoBrendan: /azp run ci
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>

Patch:
diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml
index d55e55328..f79b05cae 100644
--- a/.github/workflows/ci-go.yml
+++ b/.github/workflows/ci-go.yml
@@ -15,7 +15,7 @@ jobs:
   ci-from-docker:
     runs-on: ubuntu-latest
     container:
-      image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5
+      image: registry.access.redhat.com/ubi8/go-toolset:1.20.10
     steps:
     - name: Checkout repository
       uses: actions/checkout@v4
@@ -32,7 +32,7 @@ jobs:
   vendor-check:
     runs-on: ubuntu-latest
     container:
-      image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5
+      image: registry.access.redhat.com/ubi8/go-toolset:1.20.10
     steps:
     - name: Checkout repository
       uses: actions/checkout@v4
@@ -44,7 +44,7 @@ jobs:
   generate-check:
     runs-on: ubuntu-latest
     container:
-      image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5
+      image: registry.access.redhat.com/ubi8/go-toolset:1.20.10
     steps:
     - name: Checkout repository
       uses: actions/checkout@v4
diff --git a/.pipelines/ci.yml b/.pipelines/ci.yml
index ecdda2eba..3564656b4 100644
--- a/.pipelines/ci.yml
+++ b/.pipelines/ci.yml
@@ -22,7 +22,7 @@ pr:
 resources:
   containers:
     - container: golang
-      image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5
+      image: registry.access.redhat.com/ubi8/go-toolset:1.20.10
       options: --user=0
     - container: python
       image: registry.access.redhat.com/ubi8/python-39:latest
diff --git a/.pipelines/onebranch/pipeline.buildrp.official.yml b/.pipelines/onebranch/pipeline.buildrp.official.yml
index 6eed198f4..b95d8ba98 100644
--- a/.pipelines/onebranch/pipeline.buildrp.official.yml
+++ b/.pipelines/onebranch/pipeline.buildrp.official.yml
@@ -14,7 +14,7 @@ pr: none
 variables:
   Cdp_Definition_Build_Count: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning
   ONEBRANCH_AME_ACR_LOGIN: cdpxb8e9ef87cd634085ab141c637806568c00.azurecr.io
-  LinuxContainerImage: $(ONEBRANCH_AME_ACR_LOGIN)/b8e9ef87-cd63-4085-ab14-1c637806568c/official/ubi8/go-toolset:1.20.12-5 # Docker image which is used to build the project https://aka.ms/obpipelines/containers
+  LinuxContainerImage: $(ONEBRANCH_AME_ACR_LOGIN)/b8e9ef87-cd63-4085-ab14-1c637806568c/official/ubi8/go-toolset:1.20.10 # Docker image which is used to build the project https://aka.ms/obpipelines/containers
   Debian_Frontend: noninteractive
 
 resources:
diff --git a/.pipelines/onebranch/pipeline.buildrp.pullrequest.yml b/.pipelines/onebranch/pipeline.buildrp.pullrequest.yml
index 450c6cc99..62df20d5c 100644
--- a/.pipelines/onebranch/pipeline.buildrp.pullrequest.yml
+++ b/.pipelines/onebranch/pipeline.buildrp.pullrequest.yml
@@ -14,7 +14,7 @@ pr: none
 variables:
   Cdp_Definition_Build_Count: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning
   ONEBRANCH_AME_ACR_LOGIN: cdpxb8e9ef87cd634085ab141c637806568c00.azurecr.io
-  LinuxContainerImage: $(ONEBRANCH_AME_ACR_LOGIN)/b8e9ef87-cd63-4085-ab14-1c637806568c/official/ubi8/go-toolset:1.20.12-5 # Docker image which is used to build the project https://aka.ms/obpipelines/containers
+  LinuxContainerImage: $(ONEBRANCH_AME_ACR_LOGIN)/b8e9ef87-cd63-4085-ab14-1c637806568c/official/ubi8/go-toolset:1.20.10 # Docker image which is used to build the project https://aka.ms/obpipelines/containers
   Debian_Frontend: noninteractive
 
 resources:
diff --git a/Dockerfile.aro-multistage b/Dockerfile.aro-multistage
index 3edac6cdb..148f32ad4 100644
--- a/Dockerfile.aro-multistage
+++ b/Dockerfile.aro-multistage
@@ -1,7 +1,7 @@
 # Uses a multi-stage container build to build the RP.
 #
 ARG REGISTRY
-FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder
+FROM ${REGISTRY}/ubi8/go-toolset:1.20.10 AS builder
 
 USER root
 ENV GOPATH=/root/go
diff --git a/Dockerfile.ci-rp b/Dockerfile.ci-rp
new file mode 100644
index 000000000..4750d88bb
--- /dev/null
+++ b/Dockerfile.ci-rp
@@ -0,0 +1,48 @@
+ARG REGISTRY
+ARG VERSION
+
+###############################################################################
+# builder is responsible for all compilation and validation of the RP
+###############################################################################
+FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 as builder
+USER root
+WORKDIR /app
+
+# golang config
+ENV GOPATH=/root/go
+ENV GOFLAGS="-tags=containers_image_openpgp,exclude_graphdriver_btrfs,exclude_graphdriver_devicemapper"
+
+# install general build tools
+RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /usr/local/bin v1.56.2
+
+# copy from least changed -> most changed to improve cache hits
+COPY go.mod go.sum ./
+COPY vendor vendor
+COPY swagger swagger
+COPY .golangci.yml ./
+COPY hack hack
+COPY cmd cmd
+COPY pkg pkg
+COPY test test
+
+# lint
+RUN golangci-lint run --verbose
+
+# build
+RUN go generate ./...
+RUN go build -ldflags "-X github.com/Azure/ARO-RP/pkg/util/version.GitCommit=${VERSION}" ./cmd/aro
+RUN go test ./test/e2e/... -tags e2e,codec.safe -c -ldflags "-X github.com/Azure/ARO-RP/pkg/util/version.GitCommit=${VERSION}" -o e2e.test
+
+# test
+RUN ARO_RUN_PKI_TESTS=nope go run gotest.tools/gotestsum@v1.11.0 --format pkgname --junitfile report.xml -- -coverprofile=cover.out ./...
+RUN hack/fips/validate-fips.sh ./aro
+
+###############################################################################
+# final is our slim image with minimal layers and tools
+###############################################################################
+FROM ${REGISTRY}/ubi8/ubi-minimal AS final
+RUN microdnf update && microdnf clean all
+COPY --from=builder /app/aro /app/e2e.test /usr/local/bin/
+ENTRYPOINT ["aro"]
+EXPOSE 2222/tcp 8080/tcp 8443/tcp 8444/tcp 8445/tcp
+USER 1000
diff --git a/Dockerfile.ci-rp.dockerignore b/Dockerfile.ci-rp.dockerignore
new file mode 100644
index 000000000..a984a1402
--- /dev/null
+++ b/Dockerfile.ci-rp.dockerignore
@@ -0,0 +1,13 @@
+# ignore everything
+*
+
+# ...except these
+!.golangci.yml
+!cmd
+!go.mod
+!go.sum
+!hack
+!pkg
+!swagger
+!test
+!vendor
\ No newline at end of file
diff --git a/Dockerfile.gatekeeper b/Dockerfile.gatekeeper
index ea7a85401..1321b691b 100644
--- a/Dockerfile.gatekeeper
+++ b/Dockerfile.gatekeeper
@@ -1,5 +1,5 @@
 ARG REGISTRY
-FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder
+FROM ${REGISTRY}/ubi8/go-toolset:1.20.10-1 AS builder
 ARG GATEKEEPER_VERSION
 ENV DOWNLOAD_URL=https://github.com/open-policy-agent/gatekeeper/archive/${GATEKEEPER_VERSION}.tar.gz
 
diff --git a/Dockerfile.proxy b/Dockerfile.proxy
index 87643f947..df4355866 100644
--- a/Dockerfile.proxy
+++ b/Dockerfile.proxy
@@ -1,7 +1,7 @@
 # Uses a multi-stage container build to build the proxy
 #
 ARG REGISTRY
-FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder
+FROM ${REGISTRY}/ubi8/go-toolset:1.20.10 AS builder
 USER root
 ENV GOPATH=/root/go
 RUN mkdir -p /app
diff --git a/Makefile b/Makefile
index e0684e3c7..60ea5fdd1 100644
--- a/Makefile
+++ b/Makefile
@@ -76,6 +76,9 @@ clean:
 client: generate
 	hack/build-client.sh "${AUTOREST_IMAGE}" 2020-04-30 2021-09-01-preview 2022-04-01 2022-09-04 2023-04-01 2023-07-01-preview 2023-09-04 2023-11-22 2024-08-12-preview
 
+ci-rp: fix-macos-vendor
+	docker build . -f Dockerfile.ci-rp --ulimit=nofile=4096:4096 --build-arg REGISTRY=$(REGISTRY) --build-arg VERSION=$(VERSION) --no-cache=$(NO_CACHE)
+
 ci-portal:
 	docker build . -f Dockerfile.ci-portal --build-arg REGISTRY=$(REGISTRY) --no-cache=$(NO_CACHE)
 
@@ -92,6 +95,11 @@ discoverycache:
 	KUBECONFIG=admin.kubeconfig go run ./hack/gendiscoverycache
 	$(MAKE) generate
 
+fix-macos-vendor:
+ifeq ($(shell uname -s),Darwin)
+	mv ./vendor/github.com/Microsoft ./vendor/github.com/temp-microsoft && mv ./vendor/github.com/temp-microsoft ./vendor/github.com/microsoft || true
+endif
+
 generate:
 	go generate ./...
 
@@ -114,8 +122,7 @@ generate-kiota:
 	go run ./hack/licenses -dirs ./pkg/util/graph/graphsdk
 
 init-contrib:
-	install -v hack/git/hooks/* .git/hooks/
-	
+	cp -R hack/git/hooks/* .git/hooks/
 
 image-aro-multistage:
 	docker build --platform=linux/amd64 --network=host --no-cache -f Dockerfile.aro-multistage -t $(ARO_IMAGE) --build-arg REGISTRY=$(REGISTRY) .
@@ -274,4 +281,4 @@ vendor:
 install-go-tools:
 	go install ${GOTESTSUM}
 
-.PHONY: admin.kubeconfig aks.kubeconfig aro az ci-portal clean client deploy dev-config.yaml discoverycache generate image-aro-multistage image-fluentbit image-proxy init-contrib lint-go runlocal-rp proxy publish-image-aro-multistage publish-image-fluentbit publish-image-proxy secrets secrets-update e2e.test tunnel test-e2e test-go test-python vendor build-all validate-go unit-test-go coverage-go validate-fips install-go-tools
\ No newline at end of file
+.PHONY: admin.kubeconfig aks.kubeconfig aro az ci-portal ci-rp clean client deploy dev-config.yaml discoverycache fix-macos-vendor generate image-aro-multistage image-fluentbit image-proxy init-contrib lint-go runlocal-rp proxy publish-image-aro-multistage publish-image-fluentbit publish-image-proxy secrets secrets-update e2e.test tunnel test-e2e test-go test-python vendor build-all validate-go unit-test-go coverage-go validate-fips install-go-tools
diff --git a/docs/deploy-development-rp.md b/docs/deploy-development-rp.md
index b3c48b1e3..aa0f4ed0f 100644
--- a/docs/deploy-development-rp.md
+++ b/docs/deploy-development-rp.md
@@ -9,10 +9,24 @@
 1. Build the development `az aro` extension:
 
    ```bash
-   . ./env
    make az
    ```
 
+1. Verify that the ARO extension path is in your `az` configuration:
+
+   ```bash
+   grep -q 'dev_sources' ~/.azure/config || cat >>~/.azure/config <<EOF
+   [extension]
+   dev_sources = $PWD/python
+   EOF
+   ```
+
+   > Alternatively, if you do not want to configure the Azure CLI to utilize
+   > this extension globally, you can set the `AZURE_EXTENSION_DEV_SOURCES`
+   > environment variable to the `./python/` subfolder of this repository, e.g.
+   > in your ./env environment file, when you want to use the extension, and
+   > unset it when you do not.
+
 1. Verify the ARO extension is registered:
 
    ```bash
@@ -27,10 +41,7 @@
    ```
 
    Note: you will be able to update your development `az aro` extension in the
-   future by simply running `git pull`. If you need to use the "prod" extension,
-   what is bundled in `az` natively rather than your `./python`, you can
-   `unset AZURE_EXTENSION_DEV_SOURCES` (found in your `./env` file).
-
+   future by simply running `git pull`.
 
 
 ## Prepare your environment
diff --git a/docs/deploy-full-rp-service-in-dev.md b/docs/deploy-full-rp-service-in-dev.md
index 0d06d4cf5..1503996bc 100644
--- a/docs/deploy-full-rp-service-in-dev.md
+++ b/docs/deploy-full-rp-service-in-dev.md
@@ -267,7 +267,7 @@
         --nsg-name rp-nsg \
         --access Allow \
         --priority 500 \
-        --source-address-prefixes "$(curl --silent -4 ipecho.net/plain)/32" \
+        --source-address-prefixes "$(curl --silent ipecho.net/plain)/32" \
         --protocol Tcp \
         --destination-port-ranges 22
     ```
@@ -290,7 +290,7 @@
         --nsg-name gateway-nsg \
         --access Allow \
         --priority 500 \
-        --source-address-prefixes "$(curl --silent -4 ipecho.net/plain)/32" \
+        --source-address-prefixes "$(curl --silent ipecho.net/plain)/32" \
         --protocol Tcp \
         --destination-port-ranges 22
     ```
@@ -315,7 +315,7 @@
         --nsg-name rp-nsg \
         --access Allow \
         --priority 499 \
-        --source-address-prefixes "$(curl --silent -4 ipecho.net/plain)/32" \
+        --source-address-prefixes "$(curl --silent ipecho.net/plain)/32" \
         --protocol Tcp \
         --destination-port-ranges 443
     ```
diff --git a/docs/feature-flags.md b/docs/feature-flags.md
index 467ebf113..30fff4084 100644
--- a/docs/feature-flags.md
+++ b/docs/feature-flags.md
@@ -20,6 +20,10 @@ Subscription feature flags used by ARO-RP include:
   graduating features to production such that they only apply on our Red Hat
   engineering subscription.
 
+* Microsoft.RedHatOpenShift/SaveAROTestConfig: used on our test subscriptions to
+  cause the pkg/billing to copy billing data to a storage account so that it can
+  be validated by the billing E2E tests.
+
 Subscription feature flags are also used for API preview, INT and region
 rollout. See the RP ARM manifest for more details.
 
diff --git a/docs/prepare-your-dev-environment.md b/docs/prepare-your-dev-environment.md
index f252b2d09..d82fbd1cc 100644
--- a/docs/prepare-your-dev-environment.md
+++ b/docs/prepare-your-dev-environment.md
@@ -36,6 +36,15 @@ This document goes through the development dependencies one requires in order to
             ```bash
             export ARO_PODMAN_SOCKET=unix:///$HOME/.local/share/containers/podman/machine/qemu/podman.sock
             ```
+        
+        You will also need to ensure that podman machine has enough resources::
+
+            ```bash
+            podman machine stop
+            podman machine rm
+            podman machine init --cpus 4 --memory 5000
+            podman machine start
+            ```
 
 > __NOTE:__ If using Fedora 37+ podman and podman-docker should already be installed and enabled.
 
@@ -139,10 +148,8 @@ Make sure that `PKG_CONFIG_PATH` contains the pkgconfig files of the above packa
     cd ${GOPATH:-$HOME/go}/src/github.com/Azure/ARO-RP
     ```
 
-1. Configure local git
+1. Add standard git hooks
 
     ```bash
     make init-contrib
-    git config --global github.user <<user_name>>
     ```
-
diff --git a/env.example b/env.example
index a265550d1..7cea448a1 100644
--- a/env.example
+++ b/env.example
@@ -1,6 +1,5 @@
 export LOCATION=eastus
 export ARO_IMAGE=arointsvc.azurecr.io/aro:latest
 export NO_CACHE=false
-export AZURE_EXTENSION_DEV_SOURCES="$(pwd)/python"
 
 . secrets/env
diff --git a/pkg/api/admin/openshiftcluster.go b/pkg/api/admin/openshiftcluster.go
index d1bab4bb6..8acbeeb5b 100644
--- a/pkg/api/admin/openshiftcluster.go
+++ b/pkg/api/admin/openshiftcluster.go
@@ -43,7 +43,7 @@ type OpenShiftClusterProperties struct {
 	ClusterProfile                  ClusterProfile                   `json:"clusterProfile,omitempty"`
 	FeatureProfile                  FeatureProfile                   `json:"featureProfile,omitempty"`
 	ConsoleProfile                  ConsoleProfile                   `json:"consoleProfile,omitempty"`
-	ServicePrincipalProfile         *ServicePrincipalProfile         `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile         ServicePrincipalProfile          `json:"servicePrincipalProfile,omitempty"`
 	PlatformWorkloadIdentityProfile *PlatformWorkloadIdentityProfile `json:"platformWorkloadIdentityProfile,omitempty"`
 	NetworkProfile                  NetworkProfile                   `json:"networkProfile,omitempty"`
 	MasterProfile                   MasterProfile                    `json:"masterProfile,omitempty"`
@@ -149,9 +149,8 @@ type ConsoleProfile struct {
 
 // ServicePrincipalProfile represents a service principal profile.
 type ServicePrincipalProfile struct {
-	ClientID     string `json:"clientId,omitempty"`
-	SPObjectID   string `json:"spObjectId,omitempty"`
-	ClientSecret string `json:"clientSecret,omitempty"`
+	ClientID   string `json:"clientId,omitempty"`
+	SPObjectID string `json:"spObjectId,omitempty"`
 }
 
 // SoftwareDefinedNetwork constants.
diff --git a/pkg/api/admin/openshiftcluster_convert.go b/pkg/api/admin/openshiftcluster_convert.go
index b0a563bcf..2b24888ce 100644
--- a/pkg/api/admin/openshiftcluster_convert.go
+++ b/pkg/api/admin/openshiftcluster_convert.go
@@ -45,6 +45,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:   oc.Properties.ServicePrincipalProfile.ClientID,
+				SPObjectID: oc.Properties.ServicePrincipalProfile.SPObjectID,
+			},
 			NetworkProfile: NetworkProfile{
 				SoftwareDefinedNetwork:     SoftwareDefinedNetwork(oc.Properties.NetworkProfile.SoftwareDefinedNetwork),
 				PodCIDR:                    oc.Properties.NetworkProfile.PodCIDR,
@@ -73,14 +77,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			SPObjectID:   oc.Properties.ServicePrincipalProfile.SPObjectID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.NetworkProfile.LoadBalancerProfile != nil {
 		out.Properties.NetworkProfile.LoadBalancerProfile = &LoadBalancerProfile{}
 
@@ -279,13 +275,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.FeatureProfile.GatewayEnabled = oc.Properties.FeatureProfile.GatewayEnabled
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			SPObjectID:   oc.Properties.ServicePrincipalProfile.SPObjectID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.SPObjectID = oc.Properties.ServicePrincipalProfile.SPObjectID
 	if oc.Properties.PlatformWorkloadIdentityProfile != nil && oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities != nil {
 		out.Properties.PlatformWorkloadIdentityProfile = &api.PlatformWorkloadIdentityProfile{}
 		out.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities = make([]api.PlatformWorkloadIdentity, len(oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities))
diff --git a/pkg/api/featureflags.go b/pkg/api/featureflags.go
index 2f3ad77f1..31ef71f1f 100644
--- a/pkg/api/featureflags.go
+++ b/pkg/api/featureflags.go
@@ -4,6 +4,11 @@ package api
 // Licensed under the Apache License 2.0.
 
 const (
+	// FeatureFlagSaveAROTestConfig is the feature in the subscription that is used
+	// to indicate if we need to save ARO cluster config into the E2E
+	// StorageAccount
+	FeatureFlagSaveAROTestConfig = "Microsoft.RedHatOpenShift/SaveAROTestConfig"
+
 	// FeatureFlagMTU3900 is the feature in the subscription that causes new
 	// OpenShift cluster nodes to use the largest available Maximum Transmission
 	// Unit (MTU) on Azure virtual networks, which as of late 2021 is 3900 bytes.
diff --git a/pkg/api/openshiftcluster.go b/pkg/api/openshiftcluster.go
index 0826520d0..6095bb9bd 100644
--- a/pkg/api/openshiftcluster.go
+++ b/pkg/api/openshiftcluster.go
@@ -121,7 +121,7 @@ type OpenShiftClusterProperties struct {
 
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	PlatformWorkloadIdentityProfile *PlatformWorkloadIdentityProfile `json:"platformWorkloadIdentityProfile,omitempty"`
 
diff --git a/pkg/api/openshiftclusterdocument_example.go b/pkg/api/openshiftclusterdocument_example.go
index 9d972621f..3f2de969a 100644
--- a/pkg/api/openshiftclusterdocument_example.go
+++ b/pkg/api/openshiftclusterdocument_example.go
@@ -45,7 +45,7 @@ func ExampleOpenShiftClusterDocument() *OpenShiftClusterDocument {
 				ConsoleProfile: ConsoleProfile{
 					URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 				},
-				ServicePrincipalProfile: &ServicePrincipalProfile{
+				ServicePrincipalProfile: ServicePrincipalProfile{
 					ClientSecret: "clientSecret",
 					ClientID:     "clientId",
 				},
diff --git a/pkg/api/v20191231preview/openshiftcluster.go b/pkg/api/v20191231preview/openshiftcluster.go
index 2e9272c93..f72054b19 100644
--- a/pkg/api/v20191231preview/openshiftcluster.go
+++ b/pkg/api/v20191231preview/openshiftcluster.go
@@ -48,7 +48,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20191231preview/openshiftcluster_convert.go b/pkg/api/v20191231preview/openshiftcluster_convert.go
index d813b32b0..5967fbf8a 100644
--- a/pkg/api/v20191231preview/openshiftcluster_convert.go
+++ b/pkg/api/v20191231preview/openshiftcluster_convert.go
@@ -30,6 +30,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:     oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR: oc.Properties.NetworkProfile.ServiceCIDR,
@@ -46,13 +50,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 
@@ -133,12 +130,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.Version = oc.Properties.ClusterProfile.Version
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.MasterProfile.VMSize = api.VMSize(oc.Properties.MasterProfile.VMSize)
diff --git a/pkg/api/v20200430/openshiftcluster.go b/pkg/api/v20200430/openshiftcluster.go
index 0db22063e..b195fdc4d 100644
--- a/pkg/api/v20200430/openshiftcluster.go
+++ b/pkg/api/v20200430/openshiftcluster.go
@@ -48,7 +48,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20200430/openshiftcluster_convert.go b/pkg/api/v20200430/openshiftcluster_convert.go
index ef4cfadcc..f2ae201f8 100644
--- a/pkg/api/v20200430/openshiftcluster_convert.go
+++ b/pkg/api/v20200430/openshiftcluster_convert.go
@@ -30,6 +30,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:     oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR: oc.Properties.NetworkProfile.ServiceCIDR,
@@ -46,13 +50,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 
@@ -133,12 +130,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.Version = oc.Properties.ClusterProfile.Version
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.MasterProfile.VMSize = api.VMSize(oc.Properties.MasterProfile.VMSize)
diff --git a/pkg/api/v20210901preview/openshiftcluster.go b/pkg/api/v20210901preview/openshiftcluster.go
index 5e758c78f..b5e5d07ba 100644
--- a/pkg/api/v20210901preview/openshiftcluster.go
+++ b/pkg/api/v20210901preview/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20210901preview/openshiftcluster_convert.go b/pkg/api/v20210901preview/openshiftcluster_convert.go
index ef1cbae6b..47da24e83 100644
--- a/pkg/api/v20210901preview/openshiftcluster_convert.go
+++ b/pkg/api/v20210901preview/openshiftcluster_convert.go
@@ -30,6 +30,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:                oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR:            oc.Properties.NetworkProfile.ServiceCIDR,
@@ -49,13 +53,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 
@@ -147,12 +144,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.Version = oc.Properties.ClusterProfile.Version
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.NetworkProfile.SoftwareDefinedNetwork = api.SoftwareDefinedNetwork(oc.Properties.NetworkProfile.SoftwareDefinedNetwork)
diff --git a/pkg/api/v20220401/openshiftcluster.go b/pkg/api/v20220401/openshiftcluster.go
index 0a8217a4b..569a41aa9 100644
--- a/pkg/api/v20220401/openshiftcluster.go
+++ b/pkg/api/v20220401/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20220401/openshiftcluster_convert.go b/pkg/api/v20220401/openshiftcluster_convert.go
index 0ea465c1f..f780dce78 100644
--- a/pkg/api/v20220401/openshiftcluster_convert.go
+++ b/pkg/api/v20220401/openshiftcluster_convert.go
@@ -31,6 +31,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:     oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR: oc.Properties.NetworkProfile.ServiceCIDR,
@@ -49,13 +53,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 
@@ -148,12 +145,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.MasterProfile.VMSize = api.VMSize(oc.Properties.MasterProfile.VMSize)
diff --git a/pkg/api/v20220904/openshiftcluster.go b/pkg/api/v20220904/openshiftcluster.go
index d39218d63..113eedb66 100644
--- a/pkg/api/v20220904/openshiftcluster.go
+++ b/pkg/api/v20220904/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20220904/openshiftcluster_convert.go b/pkg/api/v20220904/openshiftcluster_convert.go
index 60137d5f2..cc3a7076c 100644
--- a/pkg/api/v20220904/openshiftcluster_convert.go
+++ b/pkg/api/v20220904/openshiftcluster_convert.go
@@ -31,6 +31,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:     oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR: oc.Properties.NetworkProfile.ServiceCIDR,
@@ -49,13 +53,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 
@@ -148,12 +145,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.MasterProfile.VMSize = api.VMSize(oc.Properties.MasterProfile.VMSize)
diff --git a/pkg/api/v20230401/openshiftcluster.go b/pkg/api/v20230401/openshiftcluster.go
index 003d562c9..ba3941502 100644
--- a/pkg/api/v20230401/openshiftcluster.go
+++ b/pkg/api/v20230401/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20230401/openshiftcluster_convert.go b/pkg/api/v20230401/openshiftcluster_convert.go
index 4f3442cb1..b23f4343c 100644
--- a/pkg/api/v20230401/openshiftcluster_convert.go
+++ b/pkg/api/v20230401/openshiftcluster_convert.go
@@ -31,6 +31,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:      oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR:  oc.Properties.NetworkProfile.ServiceCIDR,
@@ -50,13 +54,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 
@@ -149,12 +146,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.NetworkProfile.OutboundType = api.OutboundType(oc.Properties.NetworkProfile.OutboundType)
diff --git a/pkg/api/v20230701preview/openshiftcluster.go b/pkg/api/v20230701preview/openshiftcluster.go
index af891b3c3..df4ab807a 100644
--- a/pkg/api/v20230701preview/openshiftcluster.go
+++ b/pkg/api/v20230701preview/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20230701preview/openshiftcluster_convert.go b/pkg/api/v20230701preview/openshiftcluster_convert.go
index 59ff4b9dd..9b5d027e0 100644
--- a/pkg/api/v20230701preview/openshiftcluster_convert.go
+++ b/pkg/api/v20230701preview/openshiftcluster_convert.go
@@ -31,6 +31,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:      oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR:  oc.Properties.NetworkProfile.ServiceCIDR,
@@ -50,13 +54,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.NetworkProfile.LoadBalancerProfile != nil {
 		out.Properties.NetworkProfile.LoadBalancerProfile = &LoadBalancerProfile{}
 
@@ -190,12 +187,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.NetworkProfile.OutboundType = api.OutboundType(oc.Properties.NetworkProfile.OutboundType)
diff --git a/pkg/api/v20230904/openshiftcluster.go b/pkg/api/v20230904/openshiftcluster.go
index d8bb9fd29..8b03c9d80 100644
--- a/pkg/api/v20230904/openshiftcluster.go
+++ b/pkg/api/v20230904/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20230904/openshiftcluster_convert.go b/pkg/api/v20230904/openshiftcluster_convert.go
index fd8069d55..4bee9bf36 100644
--- a/pkg/api/v20230904/openshiftcluster_convert.go
+++ b/pkg/api/v20230904/openshiftcluster_convert.go
@@ -31,6 +31,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:          oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR:      oc.Properties.NetworkProfile.ServiceCIDR,
@@ -51,13 +55,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.WorkerProfiles != nil {
 		workerProfiles := oc.Properties.WorkerProfiles
 		out.Properties.WorkerProfiles = make([]WorkerProfile, 0, len(workerProfiles))
@@ -160,12 +157,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
 	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.NetworkProfile.OutboundType = api.OutboundType(oc.Properties.NetworkProfile.OutboundType)
diff --git a/pkg/api/v20231122/openshiftcluster.go b/pkg/api/v20231122/openshiftcluster.go
index 44ff2e88d..aef15848c 100644
--- a/pkg/api/v20231122/openshiftcluster.go
+++ b/pkg/api/v20231122/openshiftcluster.go
@@ -53,7 +53,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The cluster network profile.
 	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
diff --git a/pkg/api/v20231122/openshiftcluster_convert.go b/pkg/api/v20231122/openshiftcluster_convert.go
index 53c89d1da..f4f107665 100644
--- a/pkg/api/v20231122/openshiftcluster_convert.go
+++ b/pkg/api/v20231122/openshiftcluster_convert.go
@@ -31,6 +31,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:          oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR:      oc.Properties.NetworkProfile.ServiceCIDR,
@@ -51,13 +55,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.NetworkProfile.LoadBalancerProfile != nil {
 		out.Properties.NetworkProfile.LoadBalancerProfile = &LoadBalancerProfile{}
 
@@ -181,12 +178,8 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 		out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	}
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
 	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
 	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
 	out.Properties.NetworkProfile.OutboundType = api.OutboundType(oc.Properties.NetworkProfile.OutboundType)
diff --git a/pkg/api/v20240812preview/openshiftcluster.go b/pkg/api/v20240812preview/openshiftcluster.go
index 09a5a632d..519f33425 100644
--- a/pkg/api/v20240812preview/openshiftcluster.go
+++ b/pkg/api/v20240812preview/openshiftcluster.go
@@ -56,7 +56,7 @@ type OpenShiftClusterProperties struct {
 	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
 
 	// The cluster service principal profile.
-	ServicePrincipalProfile *ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
 
 	// The workload identity profile.
 	PlatformWorkloadIdentityProfile *PlatformWorkloadIdentityProfile `json:"platformWorkloadIdentityProfile,omitempty"`
diff --git a/pkg/api/v20240812preview/openshiftcluster_convert.go b/pkg/api/v20240812preview/openshiftcluster_convert.go
index 8fea9cd4a..295c77c07 100644
--- a/pkg/api/v20240812preview/openshiftcluster_convert.go
+++ b/pkg/api/v20240812preview/openshiftcluster_convert.go
@@ -32,6 +32,10 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 			ConsoleProfile: ConsoleProfile{
 				URL: oc.Properties.ConsoleProfile.URL,
 			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
 			NetworkProfile: NetworkProfile{
 				PodCIDR:          oc.Properties.NetworkProfile.PodCIDR,
 				ServiceCIDR:      oc.Properties.NetworkProfile.ServiceCIDR,
@@ -52,13 +56,6 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 		},
 	}
 
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
-
 	if oc.Properties.NetworkProfile.LoadBalancerProfile != nil {
 		out.Properties.NetworkProfile.LoadBalancerProfile = &LoadBalancerProfile{}
 
@@ -217,12 +214,9 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 		out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
 	}
 	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
-	if oc.Properties.ServicePrincipalProfile != nil {
-		out.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
-			ClientSecret: api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret),
-		}
-	}
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
+
 	if oc.Properties.PlatformWorkloadIdentityProfile != nil && oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities != nil {
 		out.Properties.PlatformWorkloadIdentityProfile = &api.PlatformWorkloadIdentityProfile{}
 		out.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities = make([]api.PlatformWorkloadIdentity, len(oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities))
diff --git a/pkg/cluster/install.go b/pkg/cluster/install.go
index 5e14d57ef..660b3f75e 100644
--- a/pkg/cluster/install.go
+++ b/pkg/cluster/install.go
@@ -27,14 +27,13 @@ import (
 	"github.com/Azure/ARO-RP/pkg/database"
 	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
 	"github.com/Azure/ARO-RP/pkg/operator/deploy"
-	utilgenerics "github.com/Azure/ARO-RP/pkg/util/generics"
 	"github.com/Azure/ARO-RP/pkg/util/restconfig"
 	"github.com/Azure/ARO-RP/pkg/util/steps"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
 const (
-	operatorCutOffVersion = "4.7.0" // OCP versions older than this will not receive ARO operator updates
+	operatorCutoffVersion = "4.7.0" // OCP versions older than this will not receive ARO operator updates
 )
 
 // AdminUpdate performs an admin update of an ARO cluster
@@ -49,37 +48,9 @@ func (m *manager) adminUpdate() []steps.Step {
 	isOperator := task == api.MaintenanceTaskOperator
 	isRenewCerts := task == api.MaintenanceTaskRenewCerts
 
-	stepsToRun := m.getZerothSteps()
-	if isEverything {
-		stepsToRun = utilgenerics.ConcatMultipleSlices(
-			stepsToRun, m.getGeneralFixesSteps(), m.getCertificateRenewalSteps(),
-		)
-		if m.shouldUpdateOperator() {
-			stepsToRun = append(stepsToRun, m.getOperatorUpdateSteps()...)
-		}
-		if m.adoptViaHive && !m.clusterWasCreatedByHive() {
-			stepsToRun = append(stepsToRun, m.getHiveAdoptionAndReconciliationSteps()...)
-		}
-	} else if isOperator {
-		if m.shouldUpdateOperator() {
-			stepsToRun = append(stepsToRun, m.getOperatorUpdateSteps()...)
-		}
-	} else if isRenewCerts {
-		stepsToRun = append(stepsToRun, m.getCertificateRenewalSteps()...)
-	}
-
-	// We don't run this on an operator-only deploy as PUCM scripts then cannot
-	// determine if the cluster has been fully admin-updated
-	// Run this last so we capture the resource provider only once the upgrade has been fully performed
-	if isEverything {
-		stepsToRun = append(stepsToRun, steps.Action(m.updateProvisionedBy))
-	}
-
-	return stepsToRun
-}
-
-func (m *manager) getZerothSteps() []steps.Step {
-	bootstrap := []steps.Step{
+	// Generic fix-up or setup actions that are fairly safe to always take, and
+	// don't require a running cluster
+	toRun := []steps.Step{
 		steps.Action(m.initializeKubernetesClients), // must be first
 		steps.Action(m.ensureBillingRecord),         // belt and braces
 		steps.Action(m.ensureDefaults),
@@ -88,87 +59,109 @@ func (m *manager) getZerothSteps() []steps.Step {
 		// struct, so we'll rebuild the fpAuthorizer and use the error catching
 		// to advance
 		steps.AuthorizationRetryingAction(m.fpAuthorizer, m.fixupClusterSPObjectID),
+		steps.Action(m.fixInfraID), // Old clusters lacks infraID in the database. Which makes code prone to errors.
 	}
 
-	// Generic fix-up actions that are fairly safe to always take, and don't require a running cluster
-	step0 := []steps.Step{
-		steps.Action(m.fixInfraID), // Old clusters lacks infraID in the database. Which makes code prone to errors.
+	if isEverything {
+		toRun = append(toRun,
+			steps.Action(m.ensureResourceGroup), // re-create RP RBAC if needed after tenant migration
+			steps.Action(m.createOrUpdateDenyAssignment),
+			steps.Action(m.ensureServiceEndpoints),
+			steps.Action(m.populateRegistryStorageAccountName), // must go before migrateStorageAccounts
+			steps.Action(m.migrateStorageAccounts),
+			steps.Action(m.fixSSH),
+			// steps.Action(m.removePrivateDNSZone), // TODO(mj): re-enable once we communicate this out
+		)
 	}
 
-	return append(bootstrap, step0...)
-}
+	if isEverything || isRenewCerts {
+		toRun = append(toRun,
+			steps.Action(m.populateDatabaseIntIP),
+		)
+	}
 
-func (m *manager) getEnsureAPIServerReadySteps() []steps.Step {
-	return []steps.Step{
+	// Make sure the VMs are switched on and we have an APIServer
+	toRun = append(toRun,
 		steps.Action(m.startVMs),
 		steps.Condition(m.apiServersReady, 30*time.Minute, true),
-	}
-}
-
-func (m *manager) getGeneralFixesSteps() []steps.Step {
-	stepsThatDontNeedAPIServer := []steps.Step{
-		steps.Action(m.ensureResourceGroup), // re-create RP RBAC if needed after tenant migration
-		steps.Action(m.createOrUpdateDenyAssignment),
-		steps.Action(m.ensureServiceEndpoints),
-		steps.Action(m.populateRegistryStorageAccountName), // must go before migrateStorageAccounts
-		steps.Action(m.migrateStorageAccounts),
-		steps.Action(m.fixSSH),
-		// steps.Action(m.removePrivateDNSZone), // TODO(mj): re-enable once we communicate this out
+	)
 
+	// Requires Kubernetes clients
+	if isEverything {
+		toRun = append(toRun,
+			steps.Action(m.fixSREKubeconfig),
+			steps.Action(m.fixUserAdminKubeconfig),
+			steps.Action(m.createOrUpdateRouterIPFromCluster),
+		)
 	}
-	stepsThatNeedAPIServer := []steps.Step{
-		steps.Action(m.fixSREKubeconfig),
-		steps.Action(m.fixUserAdminKubeconfig),
-		steps.Action(m.createOrUpdateRouterIPFromCluster),
 
-		steps.Action(m.ensureGatewayUpgrade),
-		steps.Action(m.rotateACRTokenPassword),
+	if isEverything || isRenewCerts {
+		toRun = append(toRun,
+			steps.Action(m.fixMCSCert),
+			steps.Action(m.fixMCSUserData),
+		)
+	}
 
-		steps.Action(m.populateRegistryStorageAccountName),
-		steps.Action(m.ensureMTUSize),
+	if isEverything {
+		toRun = append(toRun,
+			steps.Action(m.ensureGatewayUpgrade),
+			steps.Action(m.rotateACRTokenPassword),
+		)
 	}
-	return utilgenerics.ConcatMultipleSlices(
-		stepsThatDontNeedAPIServer,
-		m.getEnsureAPIServerReadySteps(),
-		stepsThatNeedAPIServer,
-	)
-}
 
-func (m *manager) getCertificateRenewalSteps() []steps.Step {
-	steps := []steps.Step{
-		steps.Action(m.populateDatabaseIntIP),
-		steps.Action(m.fixMCSCert),
-		steps.Action(m.fixMCSUserData),
-		steps.Action(m.configureAPIServerCertificate),
-		steps.Action(m.configureIngressCertificate),
+	if isEverything || isRenewCerts {
+		toRun = append(toRun,
+			steps.Action(m.configureAPIServerCertificate),
+			steps.Action(m.configureIngressCertificate),
+		)
+	}
 
-		steps.Action(m.initializeOperatorDeployer),
+	if isEverything {
+		toRun = append(toRun,
+			steps.Action(m.populateRegistryStorageAccountName),
+			steps.Action(m.ensureMTUSize),
+		)
+	}
 
-		steps.Action(m.renewMDSDCertificate), // Dependent on initializeOperatorDeployer.
+	if isEverything || isOperator || isRenewCerts {
+		toRun = append(toRun,
+			steps.Action(m.initializeOperatorDeployer))
 	}
-	return utilgenerics.ConcatMultipleSlices(m.getEnsureAPIServerReadySteps(), steps)
-}
 
-func (m *manager) getOperatorUpdateSteps() []steps.Step {
-	steps := []steps.Step{
-		steps.Action(m.initializeOperatorDeployer),
+	if isRenewCerts {
+		toRun = append(toRun,
+			steps.Action(m.renewMDSDCertificate),
+		)
+	}
 
-		steps.Action(m.ensureAROOperator),
+	// Update the ARO Operator
+	if (isEverything || isOperator) && m.shouldUpdateOperator() {
+		toRun = append(toRun,
+			steps.Action(m.ensureAROOperator),
+			steps.Condition(m.aroDeploymentReady, 20*time.Minute, true),
+			steps.Condition(m.ensureAROOperatorRunningDesiredVersion, 5*time.Minute, true),
+		)
+	}
 
-		// The following are dependent on initializeOperatorDeployer.
-		steps.Condition(m.aroDeploymentReady, 20*time.Minute, true),
-		steps.Condition(m.ensureAROOperatorRunningDesiredVersion, 5*time.Minute, true),
+	// Hive cluster adoption and reconciliation
+	if isEverything && m.adoptViaHive && !m.clusterWasCreatedByHive() {
+		toRun = append(toRun,
+			steps.Action(m.hiveCreateNamespace),
+			steps.Action(m.hiveEnsureResources),
+			steps.Condition(m.hiveClusterDeploymentReady, 5*time.Minute, false),
+			steps.Action(m.hiveResetCorrelationData),
+		)
 	}
-	return utilgenerics.ConcatMultipleSlices(m.getEnsureAPIServerReadySteps(), steps)
-}
 
-func (m *manager) getHiveAdoptionAndReconciliationSteps() []steps.Step {
-	return []steps.Step{
-		steps.Action(m.hiveCreateNamespace),
-		steps.Action(m.hiveEnsureResources),
-		steps.Condition(m.hiveClusterDeploymentReady, 5*time.Minute, false),
-		steps.Action(m.hiveResetCorrelationData),
+	// We don't run this on an operator-only deploy as PUCM scripts then cannot
+	// determine if the cluster has been fully admin-updated
+	if isEverything {
+		toRun = append(toRun,
+			steps.Action(m.updateProvisionedBy), // Run this last so we capture the resource provider only once the upgrade has been fully performed
+		)
 	}
+
+	return toRun
 }
 
 func (m *manager) shouldUpdateOperator() bool {
@@ -177,8 +170,8 @@ func (m *manager) shouldUpdateOperator() bool {
 		return false
 	}
 
-	cutOffVersion := semver.New(operatorCutOffVersion)
-	return cutOffVersion.Compare(*runningVersion) <= 0
+	cutoffVersion := semver.New(operatorCutoffVersion)
+	return cutoffVersion.Compare(*runningVersion) <= 0
 }
 
 func (m *manager) clusterWasCreatedByHive() bool {
@@ -194,10 +187,12 @@ func (m *manager) Update(ctx context.Context) error {
 		steps.AuthorizationRetryingAction(m.fpAuthorizer, m.validateResources),
 		steps.Action(m.initializeKubernetesClients), // All init steps are first
 		steps.Action(m.initializeOperatorDeployer),  // depends on kube clients
-		// Since ServicePrincipalProfile is now a pointer and our converters re-build the struct,
-		// our update path needs to enrich the doc with SPObjectID since it was overwritten by our API on put/patch.
-		steps.AuthorizationRetryingAction(m.fpAuthorizer, m.fixupClusterSPObjectID),
+		steps.Action(m.initializeClusterSPClients),
 
+		// TODO: this relies on an authorizer that isn't exposed in the manager
+		// struct, so we'll rebuild the fpAuthorizer and use the error catching
+		// to advance
+		steps.AuthorizationRetryingAction(m.fpAuthorizer, m.clusterSPObjectID),
 		// credentials rotation flow steps
 		steps.Action(m.createOrUpdateClusterServicePrincipalRBAC),
 		steps.Action(m.createOrUpdateDenyAssignment),
diff --git a/pkg/deploy/assets/rp-production-parameters.json b/pkg/deploy/assets/rp-production-parameters.json
index 9e40f9398..eb5855604 100644
--- a/pkg/deploy/assets/rp-production-parameters.json
+++ b/pkg/deploy/assets/rp-production-parameters.json
@@ -29,6 +29,12 @@
         "azureSecPackVSATenantId": {
             "value": ""
         },
+        "billingE2EStorageAccountId": {
+            "value": ""
+        },
+        "billingServicePrincipalId": {
+            "value": ""
+        },
         "clusterDefaultInstallerPullspec": {
             "value": ""
         },
diff --git a/pkg/deploy/assets/rp-production.json b/pkg/deploy/assets/rp-production.json
index 2fd7a5ade..22d020440 100644
--- a/pkg/deploy/assets/rp-production.json
+++ b/pkg/deploy/assets/rp-production.json
@@ -45,6 +45,14 @@
         "azureSecPackVSATenantId": {
             "type": "string"
         },
+        "billingE2EStorageAccountId": {
+            "type": "string",
+            "defaultValue": ""
+        },
+        "billingServicePrincipalId": {
+            "type": "string",
+            "defaultValue": ""
+        },
         "clusterDefaultInstallerPullspec": {
             "type": "string",
             "defaultValue": ""
@@ -508,7 +516,7 @@
                                     "autoUpgradeMinorVersion": true,
                                     "settings": {},
                                     "protectedSettings": {
-                                        "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'ACRRESOURCEID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('acrResourceId')),''')\n','ADMINAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('adminApiClientCertCommonName')),''')\n','ARMAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armApiClientCertCommonName')),''')\n','ARMCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armClientId')),''')\n','AZURECLOUDNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureCloudName')),''')\n','AZURESECPACKQUALYSURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackQualysUrl')),''')\n','AZURESECPACKVSATENANTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackVSATenantId')),''')\n','CLUSTERMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdmAccount')),''')\n','CLUSTERMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdAccount')),''')\n','CLUSTERMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdConfigVersion')),''')\n','CLUSTERMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdNamespace')),''')\n','CLUSTERPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterParentDomainName')),''')\n','DATABASEACCOUNTNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('databaseAccountName')),''')\n','DBTOKENCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenClientId')),''')\n','FLUENTBITIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fluentbitImage')),''')\n','FPCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpClientId')),''')\n','FPSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpServicePrincipalId')),''')\n','GATEWAYDOMAINS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayDomains')),''')\n','GATEWAYRESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayResourceGroupName')),''')\n','GATEWAYSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayServicePrincipalId')),''')\n','KEYVAULTDNSSUFFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultDNSSuffix')),''')\n','KEYVAULTPREFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultPrefix')),''')\n','MDMFRONTENDURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdmFrontendUrl')),''')\n','MDSDENVIRONMENT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdsdEnvironment')),''')\n','PORTALACCESSGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalAccessGroupIds')),''')\n','PORTALCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalClientId')),''')\n','PORTALELEVATEDGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalElevatedGroupIds')),''')\n','RPFEATURES=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpFeatures')),''')\n','RPIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpImage')),''')\n','RPMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdmAccount')),''')\n','RPMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdAccount')),''')\n','RPMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdConfigVersion')),''')\n','RPMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdNamespace')),''')\n','RPPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpParentDomainName')),''')\n','CLUSTERSINSTALLVIAHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersInstallViaHive')),''')\n','CLUSTERSADOPTBYHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersAdoptByHive')),''')\n','CLUSTERDEFAULTINSTALLERPULLSPEC=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterDefaultInstallerPullspec')),''')\n','USECHECKACCESS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('useCheckAccess')),''')\n','ADMINAPICABUNDLE=''',parameters('adminApiCaBundle'),'''\n','ARMAPICABUNDLE=''',parameters('armApiCaBundle'),'''\n','MDMIMAGE=''/genevamdm:2.2024.328.1744-c5fb79-20240328t1935''\n','LOCATION=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().location),''')\n','SUBSCRIPTIONID=$(base64 -d \u003c\u003c\u003c''',base64(subscription().subscriptionId),''')\n','RESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().name),''')\n','\n',base64ToString('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgojQWRkaW5nIHJldHJ5IGxvZ2ljIHRvIHl1bSBjb21tYW5kcyBpbiBvcmRlciB0byBhdm9pZCBzdGFsbGluZyBvdXQgb24gcmVzb3VyY2UgbG9ja3MKZWNobyAicnVubmluZyBSSFVJIGZpeCIKZm9yIGF0dGVtcHQgaW4gezEuLjYwfTsgZG8KICB5dW0gdXBkYXRlIC15IC0tZGlzYWJsZXJlcG89JyonIC0tZW5hYmxlcmVwbz0ncmh1aS1taWNyb3NvZnQtYXp1cmUqJyAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAicnVubmluZyB5dW0gdXBkYXRlIgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwogIHl1bSAteSAteCBXQUxpbnV4QWdlbnQgLXggV0FMaW51eEFnZW50LXVkZXYgdXBkYXRlIC0tYWxsb3dlcmFzaW5nICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNjAgXV07IHRoZW4gc2xlZXAgMzA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJleHRlbmRpbmcgcGFydGl0aW9uIHRhYmxlIgojIExpbnV4IGJsb2NrIGRldmljZXMgYXJlIGluY29uc2lzdGVudGx5IG5hbWVkCiMgaXQncyBkaWZmaWN1bHQgdG8gdGllIHRoZSBsdm0gcHYgdG8gdGhlIHBoeXNpY2FsIGRpc2sgdXNpbmcgL2Rldi9kaXNrIGZpbGVzLCB3aGljaCBpcyB3aHkgbHZzIGlzIHVzZWQgaGVyZQpwaHlzaWNhbERpc2s9IiQobHZzIC1vIGRldmljZXMgLWEgfCBoZWFkIC1uMiB8IHRhaWwgLW4xIHwgY3V0IC1kICcgJyAtZiAzIHwgY3V0IC1kIFwoIC1mIDEgfCB0ciAtZCAnWzpkaWdpdDpdJykiCmdyb3dwYXJ0ICIkcGh5c2ljYWxEaXNrIiAyCgplY2hvICJleHRlbmRpbmcgZmlsZXN5c3RlbXMiCmx2ZXh0ZW5kIC1sICsyMCVGUkVFIC9kZXYvcm9vdHZnL3Jvb3Rsdgp4ZnNfZ3Jvd2ZzIC8KCmx2ZXh0ZW5kIC1sICsxMDAlRlJFRSAvZGV2L3Jvb3R2Zy92YXJsdgp4ZnNfZ3Jvd2ZzIC92YXIKCmVjaG8gImltcG9ydGluZyBycG0gcmVwb3NpdG9yaWVzIgpycG0gLS1pbXBvcnQgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9SUE0tR1BHLUtFWS1FUEVMLTgKcnBtIC0taW1wb3J0IGh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS9rZXlzL21pY3Jvc29mdC5hc2MKCmZvciBhdHRlbXB0IGluIHsxLi42MH07IGRvCiAgeXVtIC15IGluc3RhbGwgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9lcGVsLXJlbGVhc2UtbGF0ZXN0LTgubm9hcmNoLnJwbSAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAiY29uZmlndXJpbmcgbG9ncm90YXRlIgpjYXQgPi9ldGMvbG9ncm90YXRlLmNvbmYgPDwnRU9GJwojIHNlZSAibWFuIGxvZ3JvdGF0ZSIgZm9yIGRldGFpbHMKIyByb3RhdGUgbG9nIGZpbGVzIHdlZWtseQp3ZWVrbHkKCiMga2VlcCAyIHdlZWtzIHdvcnRoIG9mIGJhY2tsb2dzCnJvdGF0ZSAyCgojIGNyZWF0ZSBuZXcgKGVtcHR5KSBsb2cgZmlsZXMgYWZ0ZXIgcm90YXRpbmcgb2xkIG9uZXMKY3JlYXRlCgojIHVzZSBkYXRlIGFzIGEgc3VmZml4IG9mIHRoZSByb3RhdGVkIGZpbGUKZGF0ZWV4dAoKIyB1bmNvbW1lbnQgdGhpcyBpZiB5b3Ugd2FudCB5b3VyIGxvZyBmaWxlcyBjb21wcmVzc2VkCmNvbXByZXNzCgojIFJQTSBwYWNrYWdlcyBkcm9wIGxvZyByb3RhdGlvbiBpbmZvcm1hdGlvbiBpbnRvIHRoaXMgZGlyZWN0b3J5CmluY2x1ZGUgL2V0Yy9sb2dyb3RhdGUuZAoKIyBubyBwYWNrYWdlcyBvd24gd3RtcCBhbmQgYnRtcCAtLSB3ZSdsbCByb3RhdGUgdGhlbSBoZXJlCi92YXIvbG9nL3d0bXAgewogICAgbW9udGhseQogICAgY3JlYXRlIDA2NjQgcm9vdCB1dG1wCiAgICAgICAgbWluc2l6ZSAxTQogICAgcm90YXRlIDEKfQoKL3Zhci9sb2cvYnRtcCB7CiAgICBtaXNzaW5nb2sKICAgIG1vbnRobHkKICAgIGNyZWF0ZSAwNjAwIHJvb3QgdXRtcAogICAgcm90YXRlIDEKfQpFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIHl1bSByZXBvc2l0b3J5IGFuZCBydW5uaW5nIHl1bSB1cGRhdGUiCmNhdCA+L2V0Yy95dW0ucmVwb3MuZC9henVyZS5yZXBvIDw8J0VPRicKW2F6dXJlLWNsaV0KbmFtZT1henVyZS1jbGkKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmUtY2xpCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPXllcwoKW2F6dXJlY29yZV0KbmFtZT1henVyZWNvcmUKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmVjb3JlCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPW5vCkVPRgoKc2VtYW5hZ2UgZmNvbnRleHQgLWEgLXQgdmFyX2xvZ190ICIvdmFyL2xvZy9qb3VybmFsKC8uKik/Igpta2RpciAtcCAvdmFyL2xvZy9qb3VybmFsCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwp5dW0gLXkgaW5zdGFsbCBjbGFtYXYgYXpzZWMtY2xhbWF2IGF6c2VjLW1vbml0b3IgYXp1cmUtY2xpIGF6dXJlLW1kc2QgYXp1cmUtc2VjdXJpdHkgcG9kbWFuIHBvZG1hbi1kb2NrZXIgb3BlbnNzbC1wZXJsIHB5dGhvbjMgJiYgYnJlYWsKICAjIGhhY2sgLSB3ZSBhcmUgaW5zdGFsbGluZyBweXRob24zIG9uIGhvc3RzIGR1ZSB0byBhbiBpc3N1ZSB3aXRoIEF6dXJlIExpbnV4IEV4dGVuc2lvbnMgaHR0cHM6Ly9naXRodWIuY29tL0F6dXJlL2F6dXJlLWxpbnV4LWV4dGVuc2lvbnMvcHVsbC8xNTA1CiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNjAgXV07IHRoZW4gc2xlZXAgMzA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgojIGh0dHBzOi8vYWNjZXNzLnJlZGhhdC5jb20vc2VjdXJpdHkvY3ZlL2N2ZS0yMDIwLTEzNDAxCmVjaG8gImFwcGx5aW5nIGZpcmV3YWxsIHJ1bGVzIgpjYXQgPi9ldGMvc3lzY3RsLmQvMDItZGlzYWJsZS1hY2NlcHQtcmEuY29uZiA8PCdFT0YnCm5ldC5pcHY2LmNvbmYuYWxsLmFjY2VwdF9yYT0wCkVPRgoKY2F0ID4vZXRjL3N5c2N0bC5kLzAxLWRpc2FibGUtY29yZS5jb25mIDw8J0VPRicKa2VybmVsLmNvcmVfcGF0dGVybiA9IHwvYmluL3RydWUKRU9GCnN5c2N0bCAtLXN5c3RlbQoKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9NDQzL3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDQvdGNwIC0tcGVybWFuZW50CmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0NS90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9MjIyMi90Y3AgLS1wZXJtYW5lbnQKCmV4cG9ydCBBWlVSRV9DTE9VRF9OQU1FPSRBWlVSRUNMT1VETkFNRQoKZWNobyAibG9nZ2luZyBpbnRvIHByb2QgYWNyIgpheiBsb2dpbiAtaSAtLWFsbG93LW5vLXN1YnNjcmlwdGlvbnMKCiMgU3VwcHJlc3MgZW11bGF0aW9uIG91dHB1dCBmb3IgcG9kbWFuIGluc3RlYWQgb2YgZG9ja2VyIGZvciBheiBhY3IgY29tcGF0YWJpbGl0eQpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKbWtkaXIgLXAgL3Jvb3QvLmRvY2tlcgpSRUdJU1RSWV9BVVRIX0ZJTEU9L3Jvb3QvLmRvY2tlci9jb25maWcuanNvbiBheiBhY3IgbG9naW4gLS1uYW1lICIkKHNlZCAtZSAnc3wuKi98fCcgPDw8IiRBQ1JSRVNPVVJDRUlEIikiCgpNRE1JTUFHRT0iJHtSUElNQUdFJSUvKn0vJHtNRE1JTUFHRSMjKi99Igpkb2NrZXIgcHVsbCAiJE1ETUlNQUdFIgpkb2NrZXIgcHVsbCAiJFJQSU1BR0UiCmRvY2tlciBwdWxsICIkRkxVRU5UQklUSU1BR0UiCgpheiBsb2dvdXQKCmVjaG8gImNvbmZpZ3VyaW5nIGZsdWVudGJpdCBzZXJ2aWNlIgpta2RpciAtcCAvZXRjL2ZsdWVudGJpdC8KbWtkaXIgLXAgL3Zhci9saWIvZmx1ZW50CgpjYXQgPi9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mIDw8J0VPRicKW0lOUFVUXQoJTmFtZSBzeXN0ZW1kCglUYWcgam91cm5hbGQKCVN5c3RlbWRfRmlsdGVyIF9DT01NPWFybwoJREIgL3Zhci9saWIvZmx1ZW50L2pvdXJuYWxkYgoKW0ZJTFRFUl0KCU5hbWUgbW9kaWZ5CglNYXRjaCBqb3VybmFsZAoJUmVtb3ZlX3dpbGRjYXJkIF8KCVJlbW92ZSBUSU1FU1RBTVAKCltGSUxURVJdCglOYW1lIHJld3JpdGVfdGFnCglNYXRjaCBqb3VybmFsZAoJUnVsZSAkTE9HS0lORCBhc3luY3FvcyBhc3luY3FvcyB0cnVlCgpbRklMVEVSXQoJTmFtZSBtb2RpZnkKCU1hdGNoIGFzeW5jcW9zCglSZW1vdmUgQ0xJRU5UX1BSSU5DSVBBTF9OQU1FCglSZW1vdmUgRklMRQoJUmVtb3ZlIENPTVBPTkVOVAoKW0ZJTFRFUl0KCU5hbWUgcmV3cml0ZV90YWcKCU1hdGNoIGpvdXJuYWxkCglSdWxlICRMT0dLSU5EIGlmeGF1ZGl0IGlmeGF1ZGl0IGZhbHNlCgpbT1VUUFVUXQoJTmFtZSBmb3J3YXJkCglNYXRjaCAqCglQb3J0IDI5MjMwCkVPRgoKZWNobyAiRkxVRU5UQklUSU1BR0U9JEZMVUVOVEJJVElNQUdFIiA+L2V0Yy9zeXNjb25maWcvZmx1ZW50Yml0CgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZmx1ZW50Yml0LnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWxTZWM9MAoKW1NlcnZpY2VdClJlc3RhcnRTZWM9MXMKRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2ZsdWVudGJpdApFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1zZWN1cml0eS1vcHQgbGFiZWw9ZGlzYWJsZSBcCiAgLS1lbnRyeXBvaW50IC9vcHQvdGQtYWdlbnQtYml0L2Jpbi90ZC1hZ2VudC1iaXQgXAogIC0tbmV0PWhvc3QgXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtdiAvZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZjovZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZiBcCiAgLXYgL3Zhci9saWIvZmx1ZW50Oi92YXIvbGliL2ZsdWVudDp6IFwKICAtdiAvdmFyL2xvZy9qb3VybmFsOi92YXIvbG9nL2pvdXJuYWw6cm8gXAogIC12IC9ldGMvbWFjaGluZS1pZDovZXRjL21hY2hpbmUtaWQ6cm8gXAogICRGTFVFTlRCSVRJTUFHRSBcCiAgLWMgL2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYKCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVOClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9NQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKbWtkaXIgL2V0Yy9hcm8tcnAKYmFzZTY0IC1kIDw8PCIkQURNSU5BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FkbWluLWNhLWJ1bmRsZS5wZW0KaWYgW1sgLW4gIiRBUk1BUElDQUJVTkRMRSIgXV07IHRoZW4KICBiYXNlNjQgLWQgPDw8IiRBUk1BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FybS1jYS1idW5kbGUucGVtCmZpCmNob3duIC1SIDEwMDA6MTAwMCAvZXRjL2Fyby1ycAoKZWNobyAiY29uZmlndXJpbmcgbWRtIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvbWRtIDw8RU9GCk1ETUZST05URU5EVVJMPSckTURNRlJPTlRFTkRVUkwnCk1ETUlNQUdFPSckTURNSU1BR0UnCk1ETVNPVVJDRUVOVklST05NRU5UPSckTE9DQVRJT04nCk1ETVNPVVJDRVJPTEU9cnAKTURNU09VUkNFUk9MRUlOU1RBTkNFPSckKGhvc3RuYW1lKScKRU9GCgpta2RpciAvdmFyL2V0dwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRtLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9tZG0KRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tZW50cnlwb2ludCAvdXNyL3NiaW4vTWV0cmljc0V4dGVuc2lvbiBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1tIDJnIFwKICAtdiAvZXRjL21kbS5wZW06L2V0Yy9tZG0ucGVtIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkTURNSU1BR0UgXAogIC1DZXJ0RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Gcm9udEVuZFVybCAkTURNRlJPTlRFTkRVUkwgXAogIC1Mb2dnZXIgQ29uc29sZSBcCiAgLUxvZ0xldmVsIFdhcm5pbmcgXAogIC1Qcml2YXRlS2V5RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Tb3VyY2VFbnZpcm9ubWVudCAkTURNU09VUkNFRU5WSVJPTk1FTlQgXAogIC1Tb3VyY2VSb2xlICRNRE1TT1VSQ0VST0xFIFwKICAtU291cmNlUm9sZUluc3RhbmNlICRNRE1TT1VSQ0VST0xFSU5TVEFOQ0UKRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgJU4KUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcnAgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tcnAgPDxFT0YKQUNSX1JFU09VUkNFX0lEPSckQUNSUkVTT1VSQ0VJRCcKQURNSU5fQVBJX0NMSUVOVF9DRVJUX0NPTU1PTl9OQU1FPSckQURNSU5BUElDTElFTlRDRVJUQ09NTU9OTkFNRScKQVJNX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRT0nJEFSTUFQSUNMSUVOVENFUlRDT01NT05OQU1FJwpBWlVSRV9BUk1fQ0xJRU5UX0lEPSckQVJNQ0xJRU5USUQnCkFaVVJFX0ZQX0NMSUVOVF9JRD0nJEZQQ0xJRU5USUQnCkFaVVJFX0ZQX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckRlBTRVJWSUNFUFJJTkNJUEFMSUQnCkNMVVNURVJfTURNX0FDQ09VTlQ9JyRDTFVTVEVSTURNQUNDT1VOVCcKQ0xVU1RFUl9NRE1fTkFNRVNQQUNFPVJQCkNMVVNURVJfTURTRF9BQ0NPVU5UPSckQ0xVU1RFUk1EU0RBQ0NPVU5UJwpDTFVTVEVSX01EU0RfQ09ORklHX1ZFUlNJT049JyRDTFVTVEVSTURTRENPTkZJR1ZFUlNJT04nCkNMVVNURVJfTURTRF9OQU1FU1BBQ0U9JyRDTFVTVEVSTURTRE5BTUVTUEFDRScKREFUQUJBU0VfQUNDT1VOVF9OQU1FPSckREFUQUJBU0VBQ0NPVU5UTkFNRScKRE9NQUlOX05BTUU9JyRMT0NBVElPTi4kQ0xVU1RFUlBBUkVOVERPTUFJTk5BTUUnCkdBVEVXQVlfRE9NQUlOUz0nJEdBVEVXQVlET01BSU5TJwpHQVRFV0FZX1JFU09VUkNFR1JPVVA9JyRHQVRFV0FZUkVTT1VSQ0VHUk9VUE5BTUUnCktFWVZBVUxUX1BSRUZJWD0nJEtFWVZBVUxUUFJFRklYJwpNRE1fQUNDT1VOVD0nJFJQTURNQUNDT1VOVCcKTURNX05BTUVTUEFDRT1SUApNRFNEX0VOVklST05NRU5UPSckTURTREVOVklST05NRU5UJwpSUF9GRUFUVVJFUz0nJFJQRkVBVFVSRVMnClJQSU1BR0U9JyRSUElNQUdFJwpBUk9fSU5TVEFMTF9WSUFfSElWRT0nJENMVVNURVJTSU5TVEFMTFZJQUhJVkUnCkFST19ISVZFX0RFRkFVTFRfSU5TVEFMTEVSX1BVTExTUEVDPSckQ0xVU1RFUkRFRkFVTFRJTlNUQUxMRVJQVUxMU1BFQycKQVJPX0FET1BUX0JZX0hJVkU9JyRDTFVTVEVSU0FET1BUQllISVZFJwpVU0VfQ0hFQ0tBQ0NFU1M9JyRVU0VDSEVDS0FDQ0VTUycKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vYXJvLXJwLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tcnAKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtZSBBQ1JfUkVTT1VSQ0VfSUQgXAogIC1lIEFETUlOX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRSBcCiAgLWUgQVJNX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRSBcCiAgLWUgQVpVUkVfQVJNX0NMSUVOVF9JRCBcCiAgLWUgQVpVUkVfRlBfQ0xJRU5UX0lEIFwKICAtZSBDTFVTVEVSX01ETV9BQ0NPVU5UIFwKICAtZSBDTFVTVEVSX01ETV9OQU1FU1BBQ0UgXAogIC1lIENMVVNURVJfTURTRF9BQ0NPVU5UIFwKICAtZSBDTFVTVEVSX01EU0RfQ09ORklHX1ZFUlNJT04gXAogIC1lIENMVVNURVJfTURTRF9OQU1FU1BBQ0UgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgRE9NQUlOX05BTUUgXAogIC1lIEdBVEVXQVlfRE9NQUlOUyBcCiAgLWUgR0FURVdBWV9SRVNPVVJDRUdST1VQIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtZSBNRFNEX0VOVklST05NRU5UIFwKICAtZSBSUF9GRUFUVVJFUyBcCiAgLWUgQVJPX0lOU1RBTExfVklBX0hJVkUgXAogIC1lIEFST19ISVZFX0RFRkFVTFRfSU5TVEFMTEVSX1BVTExTUEVDIFwKICAtZSBBUk9fQURPUFRfQllfSElWRSBcCiAgLWUgVVNFX0NIRUNLQUNDRVNTIFwKICAtbSAyZyBcCiAgLXAgNDQzOjg0NDMgXAogIC12IC9ldGMvYXJvLXJwOi9ldGMvYXJvLXJwIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIHJwCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wIC10IDM2MDAgJU4KVGltZW91dFN0b3BTZWM9MzYwMApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIGFyby1kYnRva2VuIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLWRidG9rZW4gPDxFT0YKREFUQUJBU0VfQUNDT1VOVF9OQU1FPSckREFUQUJBU0VBQ0NPVU5UTkFNRScKQVpVUkVfREJUT0tFTl9DTElFTlRfSUQ9JyREQlRPS0VOQ0xJRU5USUQnCkFaVVJFX0dBVEVXQVlfU0VSVklDRV9QUklOQ0lQQUxfSUQ9JyRHQVRFV0FZU0VSVklDRVBSSU5DSVBBTElEJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9REJUb2tlbgpSUElNQUdFPSckUlBJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vYXJvLWRidG9rZW4uc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1kYnRva2VuCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQVpVUkVfR0FURVdBWV9TRVJWSUNFX1BSSU5DSVBBTF9JRCBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBBWlVSRV9EQlRPS0VOX0NMSUVOVF9JRCBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLW0gMmcgXAogIC1wIDQ0NTo4NDQ1IFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIGRidG9rZW4KRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgLXQgMzYwMCAlTgpUaW1lb3V0U3RvcFNlYz0zNjAwClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKIyBET01BSU5fTkFNRSwgQ0xVU1RFUl9NRFNEX0FDQ09VTlQsIENMVVNURVJfTURTRF9DT05GSUdfVkVSU0lPTiwgR0FURVdBWV9ET01BSU5TLCBHQVRFV0FZX1JFU09VUkNFR1JPVVAsIE1EU0RfRU5WSVJPTk1FTlQgQ0xVU1RFUl9NRFNEX05BTUVTUEFDRQojIGFyZSBub3QgdXNlZCwgYnV0IGNhbid0IGVhc2lseSBiZSByZWZhY3RvcmVkIG91dC4gU2hvdWxkIGJlIHJldmlzaXRlZCBpbiB0aGUgZnV0dXJlLgplY2hvICJjb25maWd1cmluZyBhcm8tbW9uaXRvciBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL2Fyby1tb25pdG9yIDw8RU9GCkFaVVJFX0ZQX0NMSUVOVF9JRD0nJEZQQ0xJRU5USUQnCkRPTUFJTl9OQU1FPSckTE9DQVRJT04uJENMVVNURVJQQVJFTlRET01BSU5OQU1FJwpDTFVTVEVSX01EU0RfQUNDT1VOVD0nJENMVVNURVJNRFNEQUNDT1VOVCcKQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OPSckQ0xVU1RFUk1EU0RDT05GSUdWRVJTSU9OJwpHQVRFV0FZX0RPTUFJTlM9JyRHQVRFV0FZRE9NQUlOUycKR0FURVdBWV9SRVNPVVJDRUdST1VQPSckR0FURVdBWVJFU09VUkNFR1JPVVBOQU1FJwpNRFNEX0VOVklST05NRU5UPSckTURTREVOVklST05NRU5UJwpDTFVTVEVSX01EU0RfTkFNRVNQQUNFPSckQ0xVU1RFUk1EU0ROQU1FU1BBQ0UnCkNMVVNURVJfTURNX0FDQ09VTlQ9JyRDTFVTVEVSTURNQUNDT1VOVCcKQ0xVU1RFUl9NRE1fTkFNRVNQQUNFPUJCTQpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9QkJNClJQSU1BR0U9JyRSUElNQUdFJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tbW9uaXRvci5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvYXJvLW1vbml0b3IKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtZSBBWlVSRV9GUF9DTElFTlRfSUQgXAogIC1lIERPTUFJTl9OQU1FIFwKICAtZSBDTFVTVEVSX01EU0RfQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OIFwKICAtZSBHQVRFV0FZX0RPTUFJTlMgXAogIC1lIEdBVEVXQVlfUkVTT1VSQ0VHUk9VUCBcCiAgLWUgTURTRF9FTlZJUk9OTUVOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX05BTUVTUEFDRSBcCiAgLWUgQ0xVU1RFUl9NRE1fQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRE1fTkFNRVNQQUNFIFwKICAtZSBEQVRBQkFTRV9BQ0NPVU5UX05BTUUgXAogIC1lIEtFWVZBVUxUX1BSRUZJWCBcCiAgLWUgTURNX0FDQ09VTlQgXAogIC1lIE1ETV9OQU1FU1BBQ0UgXAogIC1tIDIuNWcgXAogIC12IC9ydW4vc3lzdGVtZC9qb3VybmFsOi9ydW4vc3lzdGVtZC9qb3VybmFsIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkUlBJTUFHRSBcCiAgbW9uaXRvcgpSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIGFyby1wb3J0YWwgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tcG9ydGFsIDw8RU9GCkFaVVJFX1BPUlRBTF9BQ0NFU1NfR1JPVVBfSURTPSckUE9SVEFMQUNDRVNTR1JPVVBJRFMnCkFaVVJFX1BPUlRBTF9DTElFTlRfSUQ9JyRQT1JUQUxDTElFTlRJRCcKQVpVUkVfUE9SVEFMX0VMRVZBVEVEX0dST1VQX0lEUz0nJFBPUlRBTEVMRVZBVEVER1JPVVBJRFMnCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCktFWVZBVUxUX1BSRUZJWD0nJEtFWVZBVUxUUFJFRklYJwpNRE1fQUNDT1VOVD0nJFJQTURNQUNDT1VOVCcKTURNX05BTUVTUEFDRT1Qb3J0YWwKUE9SVEFMX0hPU1ROQU1FPSckTE9DQVRJT04uYWRtaW4uJFJQUEFSRU5URE9NQUlOTkFNRScKUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1wb3J0YWwuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0ClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1wb3J0YWwKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtZSBBWlVSRV9QT1JUQUxfQUNDRVNTX0dST1VQX0lEUyBcCiAgLWUgQVpVUkVfUE9SVEFMX0NMSUVOVF9JRCBcCiAgLWUgQVpVUkVfUE9SVEFMX0VMRVZBVEVEX0dST1VQX0lEUyBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtZSBQT1JUQUxfSE9TVE5BTUUgXAogIC1tIDJnIFwKICAtcCA0NDQ6ODQ0NCBcCiAgLXAgMjIyMjoyMjIyIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIHBvcnRhbApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIG1kc2QgYW5kIG1kbSBzZXJ2aWNlcyIKY2hjb24gLVIgc3lzdGVtX3U6b2JqZWN0X3I6dmFyX2xvZ190OnMwIC92YXIvb3B0L21pY3Jvc29mdC9saW51eG1vbmFnZW50Cgpta2RpciAtcCAvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZQoKZm9yIHZhciBpbiAibWRzZCIgIm1kbSI7IGRvCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9kb3dubG9hZC0kdmFyLWNyZWRlbnRpYWxzLnNlcnZpY2UgPDxFT0YKW1VuaXRdCkRlc2NyaXB0aW9uPVBlcmlvZGljICR2YXIgY3JlZGVudGlhbHMgcmVmcmVzaAoKW1NlcnZpY2VdClR5cGU9b25lc2hvdApFeGVjU3RhcnQ9L3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggJHZhcgpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9kb3dubG9hZC0kdmFyLWNyZWRlbnRpYWxzLnRpbWVyIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1RpbWVyXQpPbkJvb3RTZWM9MG1pbgpPbkNhbGVuZGFyPTAvMTI6MDA6MDAKQWNjdXJhY3lTZWM9NXMKCltJbnN0YWxsXQpXYW50ZWRCeT10aW1lcnMudGFyZ2V0CkVPRgpkb25lCgpjYXQgPi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIDw8RU9GCiMhL2Jpbi9iYXNoCnNldCAtZXUKCkNPTVBPTkVOVD0iXCQxIgplY2hvICJEb3dubG9hZCBcJENPTVBPTkVOVCBjcmVkZW50aWFscyIKClRFTVBfRElSPVwkKG1rdGVtcCAtZCkKZXhwb3J0IEFaVVJFX0NPTkZJR19ESVI9XCQobWt0ZW1wIC1kKQoKZWNobyAiTG9nZ2luZyBpbnRvIEF6dXJlLi4uIgpSRVRSSUVTPTMKd2hpbGUgWyAiXCRSRVRSSUVTIiAtZ3QgMCBdOyBkbwogICAgaWYgYXogbG9naW4gLWkgLS1hbGxvdy1uby1zdWJzY3JpcHRpb25zCiAgICB0aGVuCiAgICAgICAgZWNobyAiYXogbG9naW4gc3VjY2Vzc2Z1bCIKICAgICAgICBicmVhawogICAgZWxzZQogICAgICAgIGVjaG8gImF6IGxvZ2luIGZhaWxlZC4gUmV0cnlpbmcuLi4iCiAgICAgICAgbGV0IFJFVFJJRVMtPTEKICAgICAgICBzbGVlcCA1CiAgICBmaQpkb25lCgp0cmFwICJjbGVhbnVwIiBFWElUCgpjbGVhbnVwKCkgewogIGF6IGxvZ291dAogIFtbICJcJFRFTVBfRElSIiA9fiAvdG1wLy4rIF1dICYmIHJtIC1yZiBcJFRFTVBfRElSCiAgW1sgIlwkQVpVUkVfQ09ORklHX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRBWlVSRV9DT05GSUdfRElSCn0KCmlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZG0iIF07IHRoZW4KICBDVVJSRU5UX0NFUlRfRklMRT0iL2V0Yy9tZG0ucGVtIgplbGlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZHNkIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii92YXIvbGliL3dhYWdlbnQvTWljcm9zb2Z0LkF6dXJlLktleVZhdWx0LlN0b3JlL21kc2QucGVtIgplbHNlCiAgZWNobyBJbnZhbGlkIHVzYWdlICYmIGV4aXQgMQpmaQoKU0VDUkVUX05BTUU9InJwLVwke0NPTVBPTkVOVH0iCk5FV19DRVJUX0ZJTEU9IlwkVEVNUF9ESVIvXCRDT01QT05FTlQucGVtIgpmb3IgYXR0ZW1wdCBpbiB7MS4uNX07IGRvCiAgYXoga2V5dmF1bHQgc2VjcmV0IGRvd25sb2FkIC0tZmlsZSBcJE5FV19DRVJUX0ZJTEUgLS1pZCAiaHR0cHM6Ly8kS0VZVkFVTFRQUkVGSVgtc3ZjLiRLRVlWQVVMVEROU1NVRkZJWC9zZWNyZXRzL1wkU0VDUkVUX05BTUUiICYmIGJyZWFrCiAgaWYgW1sgXCRhdHRlbXB0IC1sdCA1IF1dOyB0aGVuIHNsZWVwIDEwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKaWYgWyAtZiBcJE5FV19DRVJUX0ZJTEUgXTsgdGhlbgogIGlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZHNkIiBdOyB0aGVuCiAgICBjaG93biBzeXNsb2c6c3lzbG9nIFwkTkVXX0NFUlRfRklMRQogIGVsc2UKICAgIHNlZCAtaSAtbmUgJzEsL0VORCBDRVJUSUZJQ0FURS8gcCcgXCRORVdfQ0VSVF9GSUxFCiAgZmkKCiAgbmV3X2NlcnRfc249IlwkKG9wZW5zc2wgeDUwOSAtaW4gIlwkTkVXX0NFUlRfRklMRSIgLW5vb3V0IC1zZXJpYWwgfCBhd2sgLUY9ICd7cHJpbnQgXCQyfScpIgogIGN1cnJlbnRfY2VydF9zbj0iXCQob3BlbnNzbCB4NTA5IC1pbiAiXCRDVVJSRU5UX0NFUlRfRklMRSIgLW5vb3V0IC1zZXJpYWwgfCBhd2sgLUY9ICd7cHJpbnQgXCQyfScpIgogIGlmIFtbICEgLXogXCRuZXdfY2VydF9zbiBdXSAmJiBbWyBcJG5ld19jZXJ0X3NuICE9ICJcJGN1cnJlbnRfY2VydF9zbiIgXV07IHRoZW4KICAgIGVjaG8gdXBkYXRpbmcgY2VydGlmaWNhdGUgZm9yIFwkQ09NUE9ORU5UCiAgICBjaG1vZCAwNjAwIFwkTkVXX0NFUlRfRklMRQogICAgbXYgXCRORVdfQ0VSVF9GSUxFIFwkQ1VSUkVOVF9DRVJUX0ZJTEUKICBmaQplbHNlCiAgZWNobyBGYWlsZWQgdG8gcmVmcmVzaCBjZXJ0aWZpY2F0ZSBmb3IgXCRDT01QT05FTlQgJiYgZXhpdCAxCmZpCkVPRgoKY2htb2QgdSt4IC91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoCgpzeXN0ZW1jdGwgZW5hYmxlIGRvd25sb2FkLW1kc2QtY3JlZGVudGlhbHMudGltZXIKc3lzdGVtY3RsIGVuYWJsZSBkb3dubG9hZC1tZG0tY3JlZGVudGlhbHMudGltZXIKCi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIG1kc2QKL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggbWRtCk1EU0RDRVJUSUZJQ0FURVNBTj0kKG9wZW5zc2wgeDUwOSAtaW4gL3Zhci9saWIvd2FhZ2VudC9NaWNyb3NvZnQuQXp1cmUuS2V5VmF1bHQuU3RvcmUvbWRzZC5wZW0gLW5vb3V0IC1zdWJqZWN0IHwgc2VkIC1lICdzLy4qQ04gPSAvLycpCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnNlcnZpY2UgPDxFT0YKW1VuaXRdCkRlc2NyaXB0aW9uPVdhdGNoIGZvciBjaGFuZ2VzIGluIG1kbS5wZW0gYW5kIHJlc3RhcnRzIHRoZSBtZG0gc2VydmljZQoKW1NlcnZpY2VdClR5cGU9b25lc2hvdApFeGVjU3RhcnQ9L3Vzci9iaW4vc3lzdGVtY3RsIHJlc3RhcnQgbWRtLnNlcnZpY2UKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS93YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aCA8PEVPRgpbUGF0aF0KUGF0aE1vZGlmaWVkPS9ldGMvbWRtLnBlbQoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKc3lzdGVtY3RsIGVuYWJsZSB3YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aApzeXN0ZW1jdGwgc3RhcnQgd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnBhdGgKCm1rZGlyIC9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRzZC5zZXJ2aWNlLmQKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL21kc2Quc2VydmljZS5kL292ZXJyaWRlLmNvbmYgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CkVPRgoKY2F0ID4vZXRjL2RlZmF1bHQvbWRzZCA8PEVPRgpNRFNEX1JPTEVfUFJFRklYPS92YXIvcnVuL21kc2QvZGVmYXVsdApNRFNEX09QVElPTlM9Ii1BIC1kIC1yIFwkTURTRF9ST0xFX1BSRUZJWCIKCmV4cG9ydCBNT05JVE9SSU5HX0dDU19FTlZJUk9OTUVOVD0nJE1EU0RFTlZJUk9OTUVOVCcKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FDQ09VTlQ9JyRSUE1EU0RBQ0NPVU5UJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfUkVHSU9OPSckTE9DQVRJT04nCmV4cG9ydCBNT05JVE9SSU5HX0dDU19BVVRIX0lEX1RZUEU9QXV0aEtleVZhdWx0CmV4cG9ydCBNT05JVE9SSU5HX0dDU19BVVRIX0lEPSckTURTRENFUlRJRklDQVRFU0FOJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfTkFNRVNQQUNFPSckUlBNRFNETkFNRVNQQUNFJwpleHBvcnQgTU9OSVRPUklOR19DT05GSUdfVkVSU0lPTj0nJFJQTURTRENPTkZJR1ZFUlNJT04nCmV4cG9ydCBNT05JVE9SSU5HX1VTRV9HRU5FVkFfQ09ORklHX1NFUlZJQ0U9dHJ1ZQoKZXhwb3J0IE1PTklUT1JJTkdfVEVOQU5UPSckTE9DQVRJT04nCmV4cG9ydCBNT05JVE9SSU5HX1JPTEU9cnAKZXhwb3J0IE1PTklUT1JJTkdfUk9MRV9JTlNUQU5DRT0nJChob3N0bmFtZSknCgpleHBvcnQgTURTRF9NU0dQQUNLX1NPUlRfQ09MVU1OUz0xCkVPRgoKIyBzZXR0aW5nIE1PTklUT1JJTkdfR0NTX0FVVEhfSURfVFlQRT1BdXRoS2V5VmF1bHQgc2VlbXMgdG8gaGF2ZSBjYXVzZWQgbWRzZCBub3QKIyB0byBob25vdXIgU1NMX0NFUlRfRklMRSBhbnkgbW9yZSwgaGVhdmVuIG9ubHkga25vd3Mgd2h5Lgpta2RpciAtcCAvdXNyL2xpYi9zc2wvY2VydHMKY3NwbGl0IC1mIC91c3IvbGliL3NzbC9jZXJ0cy9jZXJ0LSAtYiAlMDNkLnBlbSAvZXRjL3BraS90bHMvY2VydHMvY2EtYnVuZGxlLmNydCAvXiQvMSB7Kn0gPi9kZXYvbnVsbApjX3JlaGFzaCAvdXNyL2xpYi9zc2wvY2VydHMKCiMgd2UgbGVhdmUgY2xpZW50SWQgYmxhbmsgYXMgbG9uZyBhcyBvbmx5IDEgbWFuYWdlZCBpZGVudGl0eSBhc3NpZ25lZCB0byB2bXNzCiMgaWYgd2UgaGF2ZSBtb3JlIHRoYW4gMSwgd2Ugd2lsbCBuZWVkIHRvIHBvcHVsYXRlIHdpdGggY2xpZW50SWQgdXNlZCBmb3Igb2ZmLW5vZGUgc2Nhbm5pbmcKY2F0ID4vZXRjL2RlZmF1bHQvdnNhLW5vZGVzY2FuLWFnZW50LmNvbmZpZyA8PEVPRgp7CiAgICAiTmljZSI6IDE5LAogICAgIlRpbWVvdXQiOiAxMDgwMCwKICAgICJDbGllbnRJZCI6ICIiLAogICAgIlRlbmFudElkIjogIiRBWlVSRVNFQ1BBQ0tWU0FURU5BTlRJRCIsCiAgICAiUXVhbHlzU3RvcmVCYXNlVXJsIjogIiRBWlVSRVNFQ1BBQ0tRVUFMWVNVUkwiLAogICAgIlByb2Nlc3NUaW1lb3V0IjogMzAwLAogICAgIkNvbW1hbmREZWxheSI6IDAKICB9CkVPRgoKZWNobyAiZW5hYmxpbmcgYXJvIHNlcnZpY2VzIgpmb3Igc2VydmljZSBpbiBhcm8tZGJ0b2tlbiBhcm8tbW9uaXRvciBhcm8tcG9ydGFsIGFyby1ycCBhdW9tcyBhenNlY2QgYXpzZWNtb25kIG1kc2QgbWRtIGNocm9ueWQgZmx1ZW50Yml0OyBkbwogIHN5c3RlbWN0bCBlbmFibGUgJHNlcnZpY2Uuc2VydmljZQpkb25lCgpmb3Igc2NhbiBpbiBiYXNlbGluZSBjbGFtYXYgc29mdHdhcmU7IGRvCiAgL3Vzci9sb2NhbC9iaW4vYXpzZWNkIGNvbmZpZyAtcyAkc2NhbiAtZCBQMUQKZG9uZQoKZWNobyAicmVib290aW5nIgpyZXN0b3JlY29uIC1SRiAvdmFyL2xvZy8qCihzbGVlcCAzMDsgcmVib290KSAmCg==')))]"
+                                        "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'ACRRESOURCEID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('acrResourceId')),''')\n','ADMINAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('adminApiClientCertCommonName')),''')\n','ARMAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armApiClientCertCommonName')),''')\n','ARMCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armClientId')),''')\n','AZURECLOUDNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureCloudName')),''')\n','AZURESECPACKQUALYSURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackQualysUrl')),''')\n','AZURESECPACKVSATENANTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackVSATenantId')),''')\n','BILLINGE2ESTORAGEACCOUNTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('billingE2EStorageAccountId')),''')\n','CLUSTERMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdmAccount')),''')\n','CLUSTERMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdAccount')),''')\n','CLUSTERMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdConfigVersion')),''')\n','CLUSTERMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdNamespace')),''')\n','CLUSTERPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterParentDomainName')),''')\n','DATABASEACCOUNTNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('databaseAccountName')),''')\n','DBTOKENCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenClientId')),''')\n','FLUENTBITIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fluentbitImage')),''')\n','FPCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpClientId')),''')\n','FPSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpServicePrincipalId')),''')\n','GATEWAYDOMAINS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayDomains')),''')\n','GATEWAYRESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayResourceGroupName')),''')\n','GATEWAYSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayServicePrincipalId')),''')\n','KEYVAULTDNSSUFFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultDNSSuffix')),''')\n','KEYVAULTPREFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultPrefix')),''')\n','MDMFRONTENDURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdmFrontendUrl')),''')\n','MDSDENVIRONMENT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdsdEnvironment')),''')\n','PORTALACCESSGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalAccessGroupIds')),''')\n','PORTALCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalClientId')),''')\n','PORTALELEVATEDGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalElevatedGroupIds')),''')\n','RPFEATURES=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpFeatures')),''')\n','RPIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpImage')),''')\n','RPMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdmAccount')),''')\n','RPMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdAccount')),''')\n','RPMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdConfigVersion')),''')\n','RPMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdNamespace')),''')\n','RPPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpParentDomainName')),''')\n','CLUSTERSINSTALLVIAHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersInstallViaHive')),''')\n','CLUSTERSADOPTBYHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersAdoptByHive')),''')\n','CLUSTERDEFAULTINSTALLERPULLSPEC=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterDefaultInstallerPullspec')),''')\n','USECHECKACCESS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('useCheckAccess')),''')\n','ADMINAPICABUNDLE=''',parameters('adminApiCaBundle'),'''\n','ARMAPICABUNDLE=''',parameters('armApiCaBundle'),'''\n','MDMIMAGE=''/genevamdm:2.2024.328.1744-c5fb79-20240328t1935''\n','LOCATION=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().location),''')\n','SUBSCRIPTIONID=$(base64 -d \u003c\u003c\u003c''',base64(subscription().subscriptionId),''')\n','RESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().name),''')\n','\n',base64ToString('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgojQWRkaW5nIHJldHJ5IGxvZ2ljIHRvIHl1bSBjb21tYW5kcyBpbiBvcmRlciB0byBhdm9pZCBzdGFsbGluZyBvdXQgb24gcmVzb3VyY2UgbG9ja3MKZWNobyAicnVubmluZyBSSFVJIGZpeCIKZm9yIGF0dGVtcHQgaW4gezEuLjYwfTsgZG8KICB5dW0gdXBkYXRlIC15IC0tZGlzYWJsZXJlcG89JyonIC0tZW5hYmxlcmVwbz0ncmh1aS1taWNyb3NvZnQtYXp1cmUqJyAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAicnVubmluZyB5dW0gdXBkYXRlIgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwogIHl1bSAteSAteCBXQUxpbnV4QWdlbnQgLXggV0FMaW51eEFnZW50LXVkZXYgdXBkYXRlIC0tYWxsb3dlcmFzaW5nICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNjAgXV07IHRoZW4gc2xlZXAgMzA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJleHRlbmRpbmcgcGFydGl0aW9uIHRhYmxlIgojIExpbnV4IGJsb2NrIGRldmljZXMgYXJlIGluY29uc2lzdGVudGx5IG5hbWVkCiMgaXQncyBkaWZmaWN1bHQgdG8gdGllIHRoZSBsdm0gcHYgdG8gdGhlIHBoeXNpY2FsIGRpc2sgdXNpbmcgL2Rldi9kaXNrIGZpbGVzLCB3aGljaCBpcyB3aHkgbHZzIGlzIHVzZWQgaGVyZQpwaHlzaWNhbERpc2s9IiQobHZzIC1vIGRldmljZXMgLWEgfCBoZWFkIC1uMiB8IHRhaWwgLW4xIHwgY3V0IC1kICcgJyAtZiAzIHwgY3V0IC1kIFwoIC1mIDEgfCB0ciAtZCAnWzpkaWdpdDpdJykiCmdyb3dwYXJ0ICIkcGh5c2ljYWxEaXNrIiAyCgplY2hvICJleHRlbmRpbmcgZmlsZXN5c3RlbXMiCmx2ZXh0ZW5kIC1sICsyMCVGUkVFIC9kZXYvcm9vdHZnL3Jvb3Rsdgp4ZnNfZ3Jvd2ZzIC8KCmx2ZXh0ZW5kIC1sICsxMDAlRlJFRSAvZGV2L3Jvb3R2Zy92YXJsdgp4ZnNfZ3Jvd2ZzIC92YXIKCmVjaG8gImltcG9ydGluZyBycG0gcmVwb3NpdG9yaWVzIgpycG0gLS1pbXBvcnQgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9SUE0tR1BHLUtFWS1FUEVMLTgKcnBtIC0taW1wb3J0IGh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS9rZXlzL21pY3Jvc29mdC5hc2MKCmZvciBhdHRlbXB0IGluIHsxLi42MH07IGRvCiAgeXVtIC15IGluc3RhbGwgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9lcGVsLXJlbGVhc2UtbGF0ZXN0LTgubm9hcmNoLnJwbSAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAiY29uZmlndXJpbmcgbG9ncm90YXRlIgpjYXQgPi9ldGMvbG9ncm90YXRlLmNvbmYgPDwnRU9GJwojIHNlZSAibWFuIGxvZ3JvdGF0ZSIgZm9yIGRldGFpbHMKIyByb3RhdGUgbG9nIGZpbGVzIHdlZWtseQp3ZWVrbHkKCiMga2VlcCAyIHdlZWtzIHdvcnRoIG9mIGJhY2tsb2dzCnJvdGF0ZSAyCgojIGNyZWF0ZSBuZXcgKGVtcHR5KSBsb2cgZmlsZXMgYWZ0ZXIgcm90YXRpbmcgb2xkIG9uZXMKY3JlYXRlCgojIHVzZSBkYXRlIGFzIGEgc3VmZml4IG9mIHRoZSByb3RhdGVkIGZpbGUKZGF0ZWV4dAoKIyB1bmNvbW1lbnQgdGhpcyBpZiB5b3Ugd2FudCB5b3VyIGxvZyBmaWxlcyBjb21wcmVzc2VkCmNvbXByZXNzCgojIFJQTSBwYWNrYWdlcyBkcm9wIGxvZyByb3RhdGlvbiBpbmZvcm1hdGlvbiBpbnRvIHRoaXMgZGlyZWN0b3J5CmluY2x1ZGUgL2V0Yy9sb2dyb3RhdGUuZAoKIyBubyBwYWNrYWdlcyBvd24gd3RtcCBhbmQgYnRtcCAtLSB3ZSdsbCByb3RhdGUgdGhlbSBoZXJlCi92YXIvbG9nL3d0bXAgewogICAgbW9udGhseQogICAgY3JlYXRlIDA2NjQgcm9vdCB1dG1wCiAgICAgICAgbWluc2l6ZSAxTQogICAgcm90YXRlIDEKfQoKL3Zhci9sb2cvYnRtcCB7CiAgICBtaXNzaW5nb2sKICAgIG1vbnRobHkKICAgIGNyZWF0ZSAwNjAwIHJvb3QgdXRtcAogICAgcm90YXRlIDEKfQpFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIHl1bSByZXBvc2l0b3J5IGFuZCBydW5uaW5nIHl1bSB1cGRhdGUiCmNhdCA+L2V0Yy95dW0ucmVwb3MuZC9henVyZS5yZXBvIDw8J0VPRicKW2F6dXJlLWNsaV0KbmFtZT1henVyZS1jbGkKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmUtY2xpCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPXllcwoKW2F6dXJlY29yZV0KbmFtZT1henVyZWNvcmUKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmVjb3JlCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPW5vCkVPRgoKc2VtYW5hZ2UgZmNvbnRleHQgLWEgLXQgdmFyX2xvZ190ICIvdmFyL2xvZy9qb3VybmFsKC8uKik/Igpta2RpciAtcCAvdmFyL2xvZy9qb3VybmFsCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwp5dW0gLXkgaW5zdGFsbCBjbGFtYXYgYXpzZWMtY2xhbWF2IGF6c2VjLW1vbml0b3IgYXp1cmUtY2xpIGF6dXJlLW1kc2QgYXp1cmUtc2VjdXJpdHkgcG9kbWFuIHBvZG1hbi1kb2NrZXIgb3BlbnNzbC1wZXJsIHB5dGhvbjMgJiYgYnJlYWsKICAjIGhhY2sgLSB3ZSBhcmUgaW5zdGFsbGluZyBweXRob24zIG9uIGhvc3RzIGR1ZSB0byBhbiBpc3N1ZSB3aXRoIEF6dXJlIExpbnV4IEV4dGVuc2lvbnMgaHR0cHM6Ly9naXRodWIuY29tL0F6dXJlL2F6dXJlLWxpbnV4LWV4dGVuc2lvbnMvcHVsbC8xNTA1CiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNjAgXV07IHRoZW4gc2xlZXAgMzA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgojIGh0dHBzOi8vYWNjZXNzLnJlZGhhdC5jb20vc2VjdXJpdHkvY3ZlL2N2ZS0yMDIwLTEzNDAxCmVjaG8gImFwcGx5aW5nIGZpcmV3YWxsIHJ1bGVzIgpjYXQgPi9ldGMvc3lzY3RsLmQvMDItZGlzYWJsZS1hY2NlcHQtcmEuY29uZiA8PCdFT0YnCm5ldC5pcHY2LmNvbmYuYWxsLmFjY2VwdF9yYT0wCkVPRgoKY2F0ID4vZXRjL3N5c2N0bC5kLzAxLWRpc2FibGUtY29yZS5jb25mIDw8J0VPRicKa2VybmVsLmNvcmVfcGF0dGVybiA9IHwvYmluL3RydWUKRU9GCnN5c2N0bCAtLXN5c3RlbQoKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9NDQzL3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDQvdGNwIC0tcGVybWFuZW50CmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0NS90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9MjIyMi90Y3AgLS1wZXJtYW5lbnQKCmV4cG9ydCBBWlVSRV9DTE9VRF9OQU1FPSRBWlVSRUNMT1VETkFNRQoKZWNobyAibG9nZ2luZyBpbnRvIHByb2QgYWNyIgpheiBsb2dpbiAtaSAtLWFsbG93LW5vLXN1YnNjcmlwdGlvbnMKCiMgU3VwcHJlc3MgZW11bGF0aW9uIG91dHB1dCBmb3IgcG9kbWFuIGluc3RlYWQgb2YgZG9ja2VyIGZvciBheiBhY3IgY29tcGF0YWJpbGl0eQpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKbWtkaXIgLXAgL3Jvb3QvLmRvY2tlcgpSRUdJU1RSWV9BVVRIX0ZJTEU9L3Jvb3QvLmRvY2tlci9jb25maWcuanNvbiBheiBhY3IgbG9naW4gLS1uYW1lICIkKHNlZCAtZSAnc3wuKi98fCcgPDw8IiRBQ1JSRVNPVVJDRUlEIikiCgpNRE1JTUFHRT0iJHtSUElNQUdFJSUvKn0vJHtNRE1JTUFHRSMjKi99Igpkb2NrZXIgcHVsbCAiJE1ETUlNQUdFIgpkb2NrZXIgcHVsbCAiJFJQSU1BR0UiCmRvY2tlciBwdWxsICIkRkxVRU5UQklUSU1BR0UiCgpheiBsb2dvdXQKCmVjaG8gImNvbmZpZ3VyaW5nIGZsdWVudGJpdCBzZXJ2aWNlIgpta2RpciAtcCAvZXRjL2ZsdWVudGJpdC8KbWtkaXIgLXAgL3Zhci9saWIvZmx1ZW50CgpjYXQgPi9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mIDw8J0VPRicKW0lOUFVUXQoJTmFtZSBzeXN0ZW1kCglUYWcgam91cm5hbGQKCVN5c3RlbWRfRmlsdGVyIF9DT01NPWFybwoJREIgL3Zhci9saWIvZmx1ZW50L2pvdXJuYWxkYgoKW0ZJTFRFUl0KCU5hbWUgbW9kaWZ5CglNYXRjaCBqb3VybmFsZAoJUmVtb3ZlX3dpbGRjYXJkIF8KCVJlbW92ZSBUSU1FU1RBTVAKCltGSUxURVJdCglOYW1lIHJld3JpdGVfdGFnCglNYXRjaCBqb3VybmFsZAoJUnVsZSAkTE9HS0lORCBhc3luY3FvcyBhc3luY3FvcyB0cnVlCgpbRklMVEVSXQoJTmFtZSBtb2RpZnkKCU1hdGNoIGFzeW5jcW9zCglSZW1vdmUgQ0xJRU5UX1BSSU5DSVBBTF9OQU1FCglSZW1vdmUgRklMRQoJUmVtb3ZlIENPTVBPTkVOVAoKW0ZJTFRFUl0KCU5hbWUgcmV3cml0ZV90YWcKCU1hdGNoIGpvdXJuYWxkCglSdWxlICRMT0dLSU5EIGlmeGF1ZGl0IGlmeGF1ZGl0IGZhbHNlCgpbT1VUUFVUXQoJTmFtZSBmb3J3YXJkCglNYXRjaCAqCglQb3J0IDI5MjMwCkVPRgoKZWNobyAiRkxVRU5UQklUSU1BR0U9JEZMVUVOVEJJVElNQUdFIiA+L2V0Yy9zeXNjb25maWcvZmx1ZW50Yml0CgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZmx1ZW50Yml0LnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWxTZWM9MAoKW1NlcnZpY2VdClJlc3RhcnRTZWM9MXMKRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2ZsdWVudGJpdApFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1zZWN1cml0eS1vcHQgbGFiZWw9ZGlzYWJsZSBcCiAgLS1lbnRyeXBvaW50IC9vcHQvdGQtYWdlbnQtYml0L2Jpbi90ZC1hZ2VudC1iaXQgXAogIC0tbmV0PWhvc3QgXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtdiAvZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZjovZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZiBcCiAgLXYgL3Zhci9saWIvZmx1ZW50Oi92YXIvbGliL2ZsdWVudDp6IFwKICAtdiAvdmFyL2xvZy9qb3VybmFsOi92YXIvbG9nL2pvdXJuYWw6cm8gXAogIC12IC9ldGMvbWFjaGluZS1pZDovZXRjL21hY2hpbmUtaWQ6cm8gXAogICRGTFVFTlRCSVRJTUFHRSBcCiAgLWMgL2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYKCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVOClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9NQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKbWtkaXIgL2V0Yy9hcm8tcnAKYmFzZTY0IC1kIDw8PCIkQURNSU5BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FkbWluLWNhLWJ1bmRsZS5wZW0KaWYgW1sgLW4gIiRBUk1BUElDQUJVTkRMRSIgXV07IHRoZW4KICBiYXNlNjQgLWQgPDw8IiRBUk1BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FybS1jYS1idW5kbGUucGVtCmZpCmNob3duIC1SIDEwMDA6MTAwMCAvZXRjL2Fyby1ycAoKZWNobyAiY29uZmlndXJpbmcgbWRtIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvbWRtIDw8RU9GCk1ETUZST05URU5EVVJMPSckTURNRlJPTlRFTkRVUkwnCk1ETUlNQUdFPSckTURNSU1BR0UnCk1ETVNPVVJDRUVOVklST05NRU5UPSckTE9DQVRJT04nCk1ETVNPVVJDRVJPTEU9cnAKTURNU09VUkNFUk9MRUlOU1RBTkNFPSckKGhvc3RuYW1lKScKRU9GCgpta2RpciAvdmFyL2V0dwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRtLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9tZG0KRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tZW50cnlwb2ludCAvdXNyL3NiaW4vTWV0cmljc0V4dGVuc2lvbiBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1tIDJnIFwKICAtdiAvZXRjL21kbS5wZW06L2V0Yy9tZG0ucGVtIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkTURNSU1BR0UgXAogIC1DZXJ0RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Gcm9udEVuZFVybCAkTURNRlJPTlRFTkRVUkwgXAogIC1Mb2dnZXIgQ29uc29sZSBcCiAgLUxvZ0xldmVsIFdhcm5pbmcgXAogIC1Qcml2YXRlS2V5RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Tb3VyY2VFbnZpcm9ubWVudCAkTURNU09VUkNFRU5WSVJPTk1FTlQgXAogIC1Tb3VyY2VSb2xlICRNRE1TT1VSQ0VST0xFIFwKICAtU291cmNlUm9sZUluc3RhbmNlICRNRE1TT1VSQ0VST0xFSU5TVEFOQ0UKRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgJU4KUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcnAgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tcnAgPDxFT0YKQUNSX1JFU09VUkNFX0lEPSckQUNSUkVTT1VSQ0VJRCcKQURNSU5fQVBJX0NMSUVOVF9DRVJUX0NPTU1PTl9OQU1FPSckQURNSU5BUElDTElFTlRDRVJUQ09NTU9OTkFNRScKQVJNX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRT0nJEFSTUFQSUNMSUVOVENFUlRDT01NT05OQU1FJwpBWlVSRV9BUk1fQ0xJRU5UX0lEPSckQVJNQ0xJRU5USUQnCkFaVVJFX0ZQX0NMSUVOVF9JRD0nJEZQQ0xJRU5USUQnCkFaVVJFX0ZQX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckRlBTRVJWSUNFUFJJTkNJUEFMSUQnCkJJTExJTkdfRTJFX1NUT1JBR0VfQUNDT1VOVF9JRD0nJEJJTExJTkdFMkVTVE9SQUdFQUNDT1VOVElEJwpDTFVTVEVSX01ETV9BQ0NPVU5UPSckQ0xVU1RFUk1ETUFDQ09VTlQnCkNMVVNURVJfTURNX05BTUVTUEFDRT1SUApDTFVTVEVSX01EU0RfQUNDT1VOVD0nJENMVVNURVJNRFNEQUNDT1VOVCcKQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OPSckQ0xVU1RFUk1EU0RDT05GSUdWRVJTSU9OJwpDTFVTVEVSX01EU0RfTkFNRVNQQUNFPSckQ0xVU1RFUk1EU0ROQU1FU1BBQ0UnCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkRPTUFJTl9OQU1FPSckTE9DQVRJT04uJENMVVNURVJQQVJFTlRET01BSU5OQU1FJwpHQVRFV0FZX0RPTUFJTlM9JyRHQVRFV0FZRE9NQUlOUycKR0FURVdBWV9SRVNPVVJDRUdST1VQPSckR0FURVdBWVJFU09VUkNFR1JPVVBOQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9UlAKTURTRF9FTlZJUk9OTUVOVD0nJE1EU0RFTlZJUk9OTUVOVCcKUlBfRkVBVFVSRVM9JyRSUEZFQVRVUkVTJwpSUElNQUdFPSckUlBJTUFHRScKQVJPX0lOU1RBTExfVklBX0hJVkU9JyRDTFVTVEVSU0lOU1RBTExWSUFISVZFJwpBUk9fSElWRV9ERUZBVUxUX0lOU1RBTExFUl9QVUxMU1BFQz0nJENMVVNURVJERUZBVUxUSU5TVEFMTEVSUFVMTFNQRUMnCkFST19BRE9QVF9CWV9ISVZFPSckQ0xVU1RFUlNBRE9QVEJZSElWRScKVVNFX0NIRUNLQUNDRVNTPSckVVNFQ0hFQ0tBQ0NFU1MnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1ycC5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvYXJvLXJwCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQUNSX1JFU09VUkNFX0lEIFwKICAtZSBBRE1JTl9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUUgXAogIC1lIEFSTV9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUUgXAogIC1lIEFaVVJFX0FSTV9DTElFTlRfSUQgXAogIC1lIEFaVVJFX0ZQX0NMSUVOVF9JRCBcCiAgLWUgQklMTElOR19FMkVfU1RPUkFHRV9BQ0NPVU5UX0lEIFwKICAtZSBDTFVTVEVSX01ETV9BQ0NPVU5UIFwKICAtZSBDTFVTVEVSX01ETV9OQU1FU1BBQ0UgXAogIC1lIENMVVNURVJfTURTRF9BQ0NPVU5UIFwKICAtZSBDTFVTVEVSX01EU0RfQ09ORklHX1ZFUlNJT04gXAogIC1lIENMVVNURVJfTURTRF9OQU1FU1BBQ0UgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgRE9NQUlOX05BTUUgXAogIC1lIEdBVEVXQVlfRE9NQUlOUyBcCiAgLWUgR0FURVdBWV9SRVNPVVJDRUdST1VQIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtZSBNRFNEX0VOVklST05NRU5UIFwKICAtZSBSUF9GRUFUVVJFUyBcCiAgLWUgQVJPX0lOU1RBTExfVklBX0hJVkUgXAogIC1lIEFST19ISVZFX0RFRkFVTFRfSU5TVEFMTEVSX1BVTExTUEVDIFwKICAtZSBBUk9fQURPUFRfQllfSElWRSBcCiAgLWUgVVNFX0NIRUNLQUNDRVNTIFwKICAtbSAyZyBcCiAgLXAgNDQzOjg0NDMgXAogIC12IC9ldGMvYXJvLXJwOi9ldGMvYXJvLXJwIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIHJwCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wIC10IDM2MDAgJU4KVGltZW91dFN0b3BTZWM9MzYwMApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIGFyby1kYnRva2VuIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLWRidG9rZW4gPDxFT0YKREFUQUJBU0VfQUNDT1VOVF9OQU1FPSckREFUQUJBU0VBQ0NPVU5UTkFNRScKQVpVUkVfREJUT0tFTl9DTElFTlRfSUQ9JyREQlRPS0VOQ0xJRU5USUQnCkFaVVJFX0dBVEVXQVlfU0VSVklDRV9QUklOQ0lQQUxfSUQ9JyRHQVRFV0FZU0VSVklDRVBSSU5DSVBBTElEJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9REJUb2tlbgpSUElNQUdFPSckUlBJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vYXJvLWRidG9rZW4uc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1kYnRva2VuCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQVpVUkVfR0FURVdBWV9TRVJWSUNFX1BSSU5DSVBBTF9JRCBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBBWlVSRV9EQlRPS0VOX0NMSUVOVF9JRCBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLW0gMmcgXAogIC1wIDQ0NTo4NDQ1IFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIGRidG9rZW4KRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgLXQgMzYwMCAlTgpUaW1lb3V0U3RvcFNlYz0zNjAwClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKIyBET01BSU5fTkFNRSwgQ0xVU1RFUl9NRFNEX0FDQ09VTlQsIENMVVNURVJfTURTRF9DT05GSUdfVkVSU0lPTiwgR0FURVdBWV9ET01BSU5TLCBHQVRFV0FZX1JFU09VUkNFR1JPVVAsIE1EU0RfRU5WSVJPTk1FTlQgQ0xVU1RFUl9NRFNEX05BTUVTUEFDRQojIGFyZSBub3QgdXNlZCwgYnV0IGNhbid0IGVhc2lseSBiZSByZWZhY3RvcmVkIG91dC4gU2hvdWxkIGJlIHJldmlzaXRlZCBpbiB0aGUgZnV0dXJlLgplY2hvICJjb25maWd1cmluZyBhcm8tbW9uaXRvciBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL2Fyby1tb25pdG9yIDw8RU9GCkFaVVJFX0ZQX0NMSUVOVF9JRD0nJEZQQ0xJRU5USUQnCkRPTUFJTl9OQU1FPSckTE9DQVRJT04uJENMVVNURVJQQVJFTlRET01BSU5OQU1FJwpDTFVTVEVSX01EU0RfQUNDT1VOVD0nJENMVVNURVJNRFNEQUNDT1VOVCcKQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OPSckQ0xVU1RFUk1EU0RDT05GSUdWRVJTSU9OJwpHQVRFV0FZX0RPTUFJTlM9JyRHQVRFV0FZRE9NQUlOUycKR0FURVdBWV9SRVNPVVJDRUdST1VQPSckR0FURVdBWVJFU09VUkNFR1JPVVBOQU1FJwpNRFNEX0VOVklST05NRU5UPSckTURTREVOVklST05NRU5UJwpDTFVTVEVSX01EU0RfTkFNRVNQQUNFPSckQ0xVU1RFUk1EU0ROQU1FU1BBQ0UnCkNMVVNURVJfTURNX0FDQ09VTlQ9JyRDTFVTVEVSTURNQUNDT1VOVCcKQ0xVU1RFUl9NRE1fTkFNRVNQQUNFPUJCTQpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9QkJNClJQSU1BR0U9JyRSUElNQUdFJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tbW9uaXRvci5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvYXJvLW1vbml0b3IKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtZSBBWlVSRV9GUF9DTElFTlRfSUQgXAogIC1lIERPTUFJTl9OQU1FIFwKICAtZSBDTFVTVEVSX01EU0RfQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OIFwKICAtZSBHQVRFV0FZX0RPTUFJTlMgXAogIC1lIEdBVEVXQVlfUkVTT1VSQ0VHUk9VUCBcCiAgLWUgTURTRF9FTlZJUk9OTUVOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX05BTUVTUEFDRSBcCiAgLWUgQ0xVU1RFUl9NRE1fQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRE1fTkFNRVNQQUNFIFwKICAtZSBEQVRBQkFTRV9BQ0NPVU5UX05BTUUgXAogIC1lIEtFWVZBVUxUX1BSRUZJWCBcCiAgLWUgTURNX0FDQ09VTlQgXAogIC1lIE1ETV9OQU1FU1BBQ0UgXAogIC1tIDIuNWcgXAogIC12IC9ydW4vc3lzdGVtZC9qb3VybmFsOi9ydW4vc3lzdGVtZC9qb3VybmFsIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkUlBJTUFHRSBcCiAgbW9uaXRvcgpSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIGFyby1wb3J0YWwgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tcG9ydGFsIDw8RU9GCkFaVVJFX1BPUlRBTF9BQ0NFU1NfR1JPVVBfSURTPSckUE9SVEFMQUNDRVNTR1JPVVBJRFMnCkFaVVJFX1BPUlRBTF9DTElFTlRfSUQ9JyRQT1JUQUxDTElFTlRJRCcKQVpVUkVfUE9SVEFMX0VMRVZBVEVEX0dST1VQX0lEUz0nJFBPUlRBTEVMRVZBVEVER1JPVVBJRFMnCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCktFWVZBVUxUX1BSRUZJWD0nJEtFWVZBVUxUUFJFRklYJwpNRE1fQUNDT1VOVD0nJFJQTURNQUNDT1VOVCcKTURNX05BTUVTUEFDRT1Qb3J0YWwKUE9SVEFMX0hPU1ROQU1FPSckTE9DQVRJT04uYWRtaW4uJFJQUEFSRU5URE9NQUlOTkFNRScKUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1wb3J0YWwuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0ClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1wb3J0YWwKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtZSBBWlVSRV9QT1JUQUxfQUNDRVNTX0dST1VQX0lEUyBcCiAgLWUgQVpVUkVfUE9SVEFMX0NMSUVOVF9JRCBcCiAgLWUgQVpVUkVfUE9SVEFMX0VMRVZBVEVEX0dST1VQX0lEUyBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtZSBQT1JUQUxfSE9TVE5BTUUgXAogIC1tIDJnIFwKICAtcCA0NDQ6ODQ0NCBcCiAgLXAgMjIyMjoyMjIyIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIHBvcnRhbApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIG1kc2QgYW5kIG1kbSBzZXJ2aWNlcyIKY2hjb24gLVIgc3lzdGVtX3U6b2JqZWN0X3I6dmFyX2xvZ190OnMwIC92YXIvb3B0L21pY3Jvc29mdC9saW51eG1vbmFnZW50Cgpta2RpciAtcCAvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZQoKZm9yIHZhciBpbiAibWRzZCIgIm1kbSI7IGRvCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9kb3dubG9hZC0kdmFyLWNyZWRlbnRpYWxzLnNlcnZpY2UgPDxFT0YKW1VuaXRdCkRlc2NyaXB0aW9uPVBlcmlvZGljICR2YXIgY3JlZGVudGlhbHMgcmVmcmVzaAoKW1NlcnZpY2VdClR5cGU9b25lc2hvdApFeGVjU3RhcnQ9L3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggJHZhcgpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9kb3dubG9hZC0kdmFyLWNyZWRlbnRpYWxzLnRpbWVyIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1RpbWVyXQpPbkJvb3RTZWM9MG1pbgpPbkNhbGVuZGFyPTAvMTI6MDA6MDAKQWNjdXJhY3lTZWM9NXMKCltJbnN0YWxsXQpXYW50ZWRCeT10aW1lcnMudGFyZ2V0CkVPRgpkb25lCgpjYXQgPi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIDw8RU9GCiMhL2Jpbi9iYXNoCnNldCAtZXUKCkNPTVBPTkVOVD0iXCQxIgplY2hvICJEb3dubG9hZCBcJENPTVBPTkVOVCBjcmVkZW50aWFscyIKClRFTVBfRElSPVwkKG1rdGVtcCAtZCkKZXhwb3J0IEFaVVJFX0NPTkZJR19ESVI9XCQobWt0ZW1wIC1kKQoKZWNobyAiTG9nZ2luZyBpbnRvIEF6dXJlLi4uIgpSRVRSSUVTPTMKd2hpbGUgWyAiXCRSRVRSSUVTIiAtZ3QgMCBdOyBkbwogICAgaWYgYXogbG9naW4gLWkgLS1hbGxvdy1uby1zdWJzY3JpcHRpb25zCiAgICB0aGVuCiAgICAgICAgZWNobyAiYXogbG9naW4gc3VjY2Vzc2Z1bCIKICAgICAgICBicmVhawogICAgZWxzZQogICAgICAgIGVjaG8gImF6IGxvZ2luIGZhaWxlZC4gUmV0cnlpbmcuLi4iCiAgICAgICAgbGV0IFJFVFJJRVMtPTEKICAgICAgICBzbGVlcCA1CiAgICBmaQpkb25lCgp0cmFwICJjbGVhbnVwIiBFWElUCgpjbGVhbnVwKCkgewogIGF6IGxvZ291dAogIFtbICJcJFRFTVBfRElSIiA9fiAvdG1wLy4rIF1dICYmIHJtIC1yZiBcJFRFTVBfRElSCiAgW1sgIlwkQVpVUkVfQ09ORklHX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRBWlVSRV9DT05GSUdfRElSCn0KCmlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZG0iIF07IHRoZW4KICBDVVJSRU5UX0NFUlRfRklMRT0iL2V0Yy9tZG0ucGVtIgplbGlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZHNkIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii92YXIvbGliL3dhYWdlbnQvTWljcm9zb2Z0LkF6dXJlLktleVZhdWx0LlN0b3JlL21kc2QucGVtIgplbHNlCiAgZWNobyBJbnZhbGlkIHVzYWdlICYmIGV4aXQgMQpmaQoKU0VDUkVUX05BTUU9InJwLVwke0NPTVBPTkVOVH0iCk5FV19DRVJUX0ZJTEU9IlwkVEVNUF9ESVIvXCRDT01QT05FTlQucGVtIgpmb3IgYXR0ZW1wdCBpbiB7MS4uNX07IGRvCiAgYXoga2V5dmF1bHQgc2VjcmV0IGRvd25sb2FkIC0tZmlsZSBcJE5FV19DRVJUX0ZJTEUgLS1pZCAiaHR0cHM6Ly8kS0VZVkFVTFRQUkVGSVgtc3ZjLiRLRVlWQVVMVEROU1NVRkZJWC9zZWNyZXRzL1wkU0VDUkVUX05BTUUiICYmIGJyZWFrCiAgaWYgW1sgXCRhdHRlbXB0IC1sdCA1IF1dOyB0aGVuIHNsZWVwIDEwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKaWYgWyAtZiBcJE5FV19DRVJUX0ZJTEUgXTsgdGhlbgogIGlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZHNkIiBdOyB0aGVuCiAgICBjaG93biBzeXNsb2c6c3lzbG9nIFwkTkVXX0NFUlRfRklMRQogIGVsc2UKICAgIHNlZCAtaSAtbmUgJzEsL0VORCBDRVJUSUZJQ0FURS8gcCcgXCRORVdfQ0VSVF9GSUxFCiAgZmkKCiAgbmV3X2NlcnRfc249IlwkKG9wZW5zc2wgeDUwOSAtaW4gIlwkTkVXX0NFUlRfRklMRSIgLW5vb3V0IC1zZXJpYWwgfCBhd2sgLUY9ICd7cHJpbnQgXCQyfScpIgogIGN1cnJlbnRfY2VydF9zbj0iXCQob3BlbnNzbCB4NTA5IC1pbiAiXCRDVVJSRU5UX0NFUlRfRklMRSIgLW5vb3V0IC1zZXJpYWwgfCBhd2sgLUY9ICd7cHJpbnQgXCQyfScpIgogIGlmIFtbICEgLXogXCRuZXdfY2VydF9zbiBdXSAmJiBbWyBcJG5ld19jZXJ0X3NuICE9ICJcJGN1cnJlbnRfY2VydF9zbiIgXV07IHRoZW4KICAgIGVjaG8gdXBkYXRpbmcgY2VydGlmaWNhdGUgZm9yIFwkQ09NUE9ORU5UCiAgICBjaG1vZCAwNjAwIFwkTkVXX0NFUlRfRklMRQogICAgbXYgXCRORVdfQ0VSVF9GSUxFIFwkQ1VSUkVOVF9DRVJUX0ZJTEUKICBmaQplbHNlCiAgZWNobyBGYWlsZWQgdG8gcmVmcmVzaCBjZXJ0aWZpY2F0ZSBmb3IgXCRDT01QT05FTlQgJiYgZXhpdCAxCmZpCkVPRgoKY2htb2QgdSt4IC91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoCgpzeXN0ZW1jdGwgZW5hYmxlIGRvd25sb2FkLW1kc2QtY3JlZGVudGlhbHMudGltZXIKc3lzdGVtY3RsIGVuYWJsZSBkb3dubG9hZC1tZG0tY3JlZGVudGlhbHMudGltZXIKCi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIG1kc2QKL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggbWRtCk1EU0RDRVJUSUZJQ0FURVNBTj0kKG9wZW5zc2wgeDUwOSAtaW4gL3Zhci9saWIvd2FhZ2VudC9NaWNyb3NvZnQuQXp1cmUuS2V5VmF1bHQuU3RvcmUvbWRzZC5wZW0gLW5vb3V0IC1zdWJqZWN0IHwgc2VkIC1lICdzLy4qQ04gPSAvLycpCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnNlcnZpY2UgPDxFT0YKW1VuaXRdCkRlc2NyaXB0aW9uPVdhdGNoIGZvciBjaGFuZ2VzIGluIG1kbS5wZW0gYW5kIHJlc3RhcnRzIHRoZSBtZG0gc2VydmljZQoKW1NlcnZpY2VdClR5cGU9b25lc2hvdApFeGVjU3RhcnQ9L3Vzci9iaW4vc3lzdGVtY3RsIHJlc3RhcnQgbWRtLnNlcnZpY2UKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS93YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aCA8PEVPRgpbUGF0aF0KUGF0aE1vZGlmaWVkPS9ldGMvbWRtLnBlbQoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKc3lzdGVtY3RsIGVuYWJsZSB3YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aApzeXN0ZW1jdGwgc3RhcnQgd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnBhdGgKCm1rZGlyIC9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRzZC5zZXJ2aWNlLmQKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL21kc2Quc2VydmljZS5kL292ZXJyaWRlLmNvbmYgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CkVPRgoKY2F0ID4vZXRjL2RlZmF1bHQvbWRzZCA8PEVPRgpNRFNEX1JPTEVfUFJFRklYPS92YXIvcnVuL21kc2QvZGVmYXVsdApNRFNEX09QVElPTlM9Ii1BIC1kIC1yIFwkTURTRF9ST0xFX1BSRUZJWCIKCmV4cG9ydCBNT05JVE9SSU5HX0dDU19FTlZJUk9OTUVOVD0nJE1EU0RFTlZJUk9OTUVOVCcKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FDQ09VTlQ9JyRSUE1EU0RBQ0NPVU5UJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfUkVHSU9OPSckTE9DQVRJT04nCmV4cG9ydCBNT05JVE9SSU5HX0dDU19BVVRIX0lEX1RZUEU9QXV0aEtleVZhdWx0CmV4cG9ydCBNT05JVE9SSU5HX0dDU19BVVRIX0lEPSckTURTRENFUlRJRklDQVRFU0FOJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfTkFNRVNQQUNFPSckUlBNRFNETkFNRVNQQUNFJwpleHBvcnQgTU9OSVRPUklOR19DT05GSUdfVkVSU0lPTj0nJFJQTURTRENPTkZJR1ZFUlNJT04nCmV4cG9ydCBNT05JVE9SSU5HX1VTRV9HRU5FVkFfQ09ORklHX1NFUlZJQ0U9dHJ1ZQoKZXhwb3J0IE1PTklUT1JJTkdfVEVOQU5UPSckTE9DQVRJT04nCmV4cG9ydCBNT05JVE9SSU5HX1JPTEU9cnAKZXhwb3J0IE1PTklUT1JJTkdfUk9MRV9JTlNUQU5DRT0nJChob3N0bmFtZSknCgpleHBvcnQgTURTRF9NU0dQQUNLX1NPUlRfQ09MVU1OUz0xCkVPRgoKIyBzZXR0aW5nIE1PTklUT1JJTkdfR0NTX0FVVEhfSURfVFlQRT1BdXRoS2V5VmF1bHQgc2VlbXMgdG8gaGF2ZSBjYXVzZWQgbWRzZCBub3QKIyB0byBob25vdXIgU1NMX0NFUlRfRklMRSBhbnkgbW9yZSwgaGVhdmVuIG9ubHkga25vd3Mgd2h5Lgpta2RpciAtcCAvdXNyL2xpYi9zc2wvY2VydHMKY3NwbGl0IC1mIC91c3IvbGliL3NzbC9jZXJ0cy9jZXJ0LSAtYiAlMDNkLnBlbSAvZXRjL3BraS90bHMvY2VydHMvY2EtYnVuZGxlLmNydCAvXiQvMSB7Kn0gPi9kZXYvbnVsbApjX3JlaGFzaCAvdXNyL2xpYi9zc2wvY2VydHMKCiMgd2UgbGVhdmUgY2xpZW50SWQgYmxhbmsgYXMgbG9uZyBhcyBvbmx5IDEgbWFuYWdlZCBpZGVudGl0eSBhc3NpZ25lZCB0byB2bXNzCiMgaWYgd2UgaGF2ZSBtb3JlIHRoYW4gMSwgd2Ugd2lsbCBuZWVkIHRvIHBvcHVsYXRlIHdpdGggY2xpZW50SWQgdXNlZCBmb3Igb2ZmLW5vZGUgc2Nhbm5pbmcKY2F0ID4vZXRjL2RlZmF1bHQvdnNhLW5vZGVzY2FuLWFnZW50LmNvbmZpZyA8PEVPRgp7CiAgICAiTmljZSI6IDE5LAogICAgIlRpbWVvdXQiOiAxMDgwMCwKICAgICJDbGllbnRJZCI6ICIiLAogICAgIlRlbmFudElkIjogIiRBWlVSRVNFQ1BBQ0tWU0FURU5BTlRJRCIsCiAgICAiUXVhbHlzU3RvcmVCYXNlVXJsIjogIiRBWlVSRVNFQ1BBQ0tRVUFMWVNVUkwiLAogICAgIlByb2Nlc3NUaW1lb3V0IjogMzAwLAogICAgIkNvbW1hbmREZWxheSI6IDAKICB9CkVPRgoKZWNobyAiZW5hYmxpbmcgYXJvIHNlcnZpY2VzIgpmb3Igc2VydmljZSBpbiBhcm8tZGJ0b2tlbiBhcm8tbW9uaXRvciBhcm8tcG9ydGFsIGFyby1ycCBhdW9tcyBhenNlY2QgYXpzZWNtb25kIG1kc2QgbWRtIGNocm9ueWQgZmx1ZW50Yml0OyBkbwogIHN5c3RlbWN0bCBlbmFibGUgJHNlcnZpY2Uuc2VydmljZQpkb25lCgpmb3Igc2NhbiBpbiBiYXNlbGluZSBjbGFtYXYgc29mdHdhcmU7IGRvCiAgL3Vzci9sb2NhbC9iaW4vYXpzZWNkIGNvbmZpZyAtcyAkc2NhbiAtZCBQMUQKZG9uZQoKZWNobyAicmVib290aW5nIgpyZXN0b3JlY29uIC1SRiAvdmFyL2xvZy8qCihzbGVlcCAzMDsgcmVib290KSAmCg==')))]"
                                     }
                                 }
                             }
@@ -670,6 +678,21 @@
                 "[resourceId('Microsoft.Network/loadBalancers', 'rp-lb')]"
             ]
         },
+        {
+            "name": "[concat(parameters('databaseAccountName'), '/Microsoft.Authorization/', guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('billingServicePrincipalId') , 'Billing / DocumentDB Account Contributor'))]",
+            "type": "Microsoft.DocumentDB/databaseAccounts/providers/roleAssignments",
+            "properties": {
+                "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]",
+                "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5bd9cd88-fe45-4216-938b-f97437e15450')]",
+                "principalId": "[parameters('billingServicePrincipalId')]",
+                "principalType": "ServicePrincipal"
+            },
+            "condition": "[greater(length(parameters('billingServicePrincipalId')), 0)]",
+            "apiVersion": "2018-09-01-preview",
+            "dependsOn": [
+                "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]"
+            ]
+        },
         {
             "properties": {
                 "allowVirtualNetworkAccess": true,
diff --git a/pkg/deploy/config.go b/pkg/deploy/config.go
index 998d11095..449cd6990 100644
--- a/pkg/deploy/config.go
+++ b/pkg/deploy/config.go
@@ -48,6 +48,8 @@ type Configuration struct {
 	ARMAPICABundle                     *string                `json:"armApiCaBundle,omitempty"`
 	ARMAPIClientCertCommonName         *string                `json:"armApiClientCertCommonName,omitempty"`
 	ARMClientID                        *string                `json:"armClientId,omitempty"`
+	BillingE2EStorageAccountID         *string                `json:"billingE2EStorageAccountId,omitempty"`
+	BillingServicePrincipalID          *string                `json:"billingServicePrincipalId,omitempty"`
 	ClusterMDMAccount                  *string                `json:"clusterMdmAccount,omitempty" value:"required"`
 	ClusterMDSDAccount                 *string                `json:"clusterMdsdAccount,omitempty" value:"required"`
 	ClusterMDSDConfigVersion           *string                `json:"clusterMdsdConfigVersion,omitempty" value:"required"`
diff --git a/pkg/deploy/generator/resources_rp.go b/pkg/deploy/generator/resources_rp.go
index 38f959042..035965f1e 100644
--- a/pkg/deploy/generator/resources_rp.go
+++ b/pkg/deploy/generator/resources_rp.go
@@ -457,6 +457,7 @@ func (g *generator) rpVMSS() *arm.Resource {
 		"azureCloudName",
 		"azureSecPackQualysUrl",
 		"azureSecPackVSATenantId",
+		"billingE2EStorageAccountId",
 		"clusterMdmAccount",
 		"clusterMdsdAccount",
 		"clusterMdsdConfigVersion",
@@ -1443,6 +1444,19 @@ func (g *generator) rpRBAC() []*arm.Resource {
 	}
 }
 
+func (g *generator) rpBillingContributorRbac() []*arm.Resource {
+	return []*arm.Resource{
+		rbac.ResourceRoleAssignmentWithName(
+			rbac.RoleDocumentDBAccountContributor,
+			"parameters('billingServicePrincipalId')",
+			"Microsoft.DocumentDB/databaseAccounts",
+			"parameters('databaseAccountName')",
+			"concat(parameters('databaseAccountName'), '/Microsoft.Authorization/', guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('billingServicePrincipalId') , 'Billing / DocumentDB Account Contributor'))",
+			"[greater(length(parameters('billingServicePrincipalId')), 0)]",
+		),
+	}
+}
+
 func (g *generator) rpACR() *arm.Resource {
 	return &arm.Resource{
 		Resource: &mgmtcontainerregistry.Registry{
diff --git a/pkg/deploy/generator/scripts/rpVMSS.sh b/pkg/deploy/generator/scripts/rpVMSS.sh
index 78149f304..ad7f61b41 100644
--- a/pkg/deploy/generator/scripts/rpVMSS.sh
+++ b/pkg/deploy/generator/scripts/rpVMSS.sh
@@ -272,6 +272,7 @@ ARM_API_CLIENT_CERT_COMMON_NAME='$ARMAPICLIENTCERTCOMMONNAME'
 AZURE_ARM_CLIENT_ID='$ARMCLIENTID'
 AZURE_FP_CLIENT_ID='$FPCLIENTID'
 AZURE_FP_SERVICE_PRINCIPAL_ID='$FPSERVICEPRINCIPALID'
+BILLING_E2E_STORAGE_ACCOUNT_ID='$BILLINGE2ESTORAGEACCOUNTID'
 CLUSTER_MDM_ACCOUNT='$CLUSTERMDMACCOUNT'
 CLUSTER_MDM_NAMESPACE=RP
 CLUSTER_MDSD_ACCOUNT='$CLUSTERMDSDACCOUNT'
@@ -311,6 +312,7 @@ ExecStart=/usr/bin/docker run \
   -e ARM_API_CLIENT_CERT_COMMON_NAME \
   -e AZURE_ARM_CLIENT_ID \
   -e AZURE_FP_CLIENT_ID \
+  -e BILLING_E2E_STORAGE_ACCOUNT_ID \
   -e CLUSTER_MDM_ACCOUNT \
   -e CLUSTER_MDM_NAMESPACE \
   -e CLUSTER_MDSD_ACCOUNT \
diff --git a/pkg/deploy/generator/templates_rp.go b/pkg/deploy/generator/templates_rp.go
index d5506af64..7452d0fb2 100644
--- a/pkg/deploy/generator/templates_rp.go
+++ b/pkg/deploy/generator/templates_rp.go
@@ -41,6 +41,8 @@ func (g *generator) rpTemplate() *arm.Template {
 			"azureCloudName",
 			"azureSecPackQualysUrl",
 			"azureSecPackVSATenantId",
+			"billingE2EStorageAccountId",
+			"billingServicePrincipalId",
 			"clusterMdmAccount",
 			"clusterMdsdAccount",
 			"clusterMdsdConfigVersion",
@@ -98,6 +100,8 @@ func (g *generator) rpTemplate() *arm.Template {
 		case "armApiCaBundle",
 			"armApiClientCertCommonName",
 			"armClientId",
+			"billingServicePrincipalId",
+			"billingE2EStorageAccountId",
 			"gatewayDomains",
 			"rpFeatures":
 			p.DefaultValue = ""
@@ -173,6 +177,7 @@ func (g *generator) rpTemplate() *arm.Template {
 			g.rpLBAlert(67.0, 3, "rp-degraded-alert", "PT15M", "PT6H", "DipAvailability"),     // 1/3 backend down for 1h or 2/3 down for 3h in the last 6h
 			g.rpLBAlert(33.0, 2, "rp-vnet-alert", "PT5M", "PT5M", "VipAvailability"))          // this will trigger only if the Azure network infrastructure between the loadBalancers and VMs is down for 3.5min
 		// more on alerts https://msazure.visualstudio.com/AzureRedHatOpenShift/_wiki/wikis/ARO.wiki/53765/WIP-Alerting
+		t.Resources = append(t.Resources, g.rpBillingContributorRbac()...)
 
 		t.Resources = append(t.Resources,
 			g.virtualNetworkPeering("rp-vnet/peering-gateway-vnet", "[resourceId(parameters('gatewayResourceGroupName'), 'Microsoft.Network/virtualNetworks', 'gateway-vnet')]", false, false, nil),
diff --git a/pkg/frontend/openshiftcluster_putorpatch.go b/pkg/frontend/openshiftcluster_putorpatch.go
index 1fa6f26fd..9390a1f8f 100644
--- a/pkg/frontend/openshiftcluster_putorpatch.go
+++ b/pkg/frontend/openshiftcluster_putorpatch.go
@@ -126,7 +126,7 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, log *logrus.
 	// Our base structure for unmarshal is skeleton document with values we
 	// think is required. We expect payload to have everything else required.
 	case http.MethodPut:
-		document := &api.OpenShiftCluster{
+		ext = converter.ToExternal(&api.OpenShiftCluster{
 			ID:   doc.OpenShiftCluster.ID,
 			Name: doc.OpenShiftCluster.Name,
 			Type: doc.OpenShiftCluster.Type,
@@ -136,16 +136,12 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, log *logrus.
 					PullSecret: doc.OpenShiftCluster.Properties.ClusterProfile.PullSecret,
 					Version:    doc.OpenShiftCluster.Properties.ClusterProfile.Version,
 				},
+				ServicePrincipalProfile: api.ServicePrincipalProfile{
+					ClientSecret: doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientSecret,
+				},
 			},
 			SystemData: doc.OpenShiftCluster.SystemData,
-		}
-
-		if doc.OpenShiftCluster.Properties.ServicePrincipalProfile != nil {
-			document.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{}
-			document.Properties.ServicePrincipalProfile.ClientSecret = doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientSecret
-		}
-
-		ext = converter.ToExternal(document)
+		})
 
 	// In case of PATCH we take current cluster document, which is enriched
 	// from the cluster and use it as base for unmarshal. So customer can
@@ -194,11 +190,7 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, log *logrus.
 		doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID = strings.ToLower(doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID)
 
 		doc.ClusterResourceGroupIDKey = strings.ToLower(doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID)
-
-		if doc.OpenShiftCluster.Properties.ServicePrincipalProfile != nil {
-			doc.ClientIDKey = strings.ToLower(doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientID)
-		}
-
+		doc.ClientIDKey = strings.ToLower(doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientID)
 		doc.OpenShiftCluster.Properties.ProvisioningState = api.ProvisioningStateCreating
 
 		doc.Bucket, err = f.bucketAllocator.Allocate()
@@ -243,10 +235,7 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, log *logrus.
 	// We remove sensitive data from document to prevent sensitive data being
 	// returned to the customer.
 	doc.OpenShiftCluster.Properties.ClusterProfile.PullSecret = ""
-
-	if doc.OpenShiftCluster.Properties.ServicePrincipalProfile != nil {
-		doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientSecret = ""
-	}
+	doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientSecret = ""
 
 	// We don't return enriched worker profile data on PUT/PATCH operations
 	doc.OpenShiftCluster.Properties.WorkerProfilesStatus = nil
diff --git a/pkg/util/billing/billing.go b/pkg/util/billing/billing.go
index 935f9abbe..a8e97b8a2 100644
--- a/pkg/util/billing/billing.go
+++ b/pkg/util/billing/billing.go
@@ -4,15 +4,28 @@ package billing
 // Licensed under the Apache License 2.0.
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
 	"net/http"
+	"os"
+	"strings"
 
+	azstorage "github.com/Azure/azure-sdk-for-go/storage"
+	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
 	"github.com/Azure/ARO-RP/pkg/env"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/storage"
+	"github.com/Azure/ARO-RP/pkg/util/feature"
+)
+
+const (
+	tenantIDMSFT = "72f988bf-86f1-41af-91ab-2d7cd011db47"
+	tenantIDAME  = "33e01921-4d64-4f8c-a055-5bdaffd5e33d"
 )
 
 type Manager interface {
@@ -21,19 +34,58 @@ type Manager interface {
 }
 
 type manager struct {
-	billingDB database.Billing
-	log       *logrus.Entry
+	storageClient *azstorage.Client
+	billingDB     database.Billing
+	subDB         database.Subscriptions
+	log           *logrus.Entry
 }
 
 func NewManager(env env.Interface, billing database.Billing, sub database.Subscriptions, log *logrus.Entry) (Manager, error) {
+	storageClient, err := storageClient(env, billing, sub, log)
+	if err != nil {
+		return nil, err
+	}
+
 	return &manager{
-		billingDB: billing,
-		log:       log,
+		storageClient: storageClient,
+		subDB:         sub,
+		billingDB:     billing,
+		log:           log,
 	}, nil
 }
 
+func storageClient(env env.Interface, billing database.Billing, sub database.Subscriptions, log *logrus.Entry) (*azstorage.Client, error) {
+	if os.Getenv("BILLING_E2E_STORAGE_ACCOUNT_ID") == "" {
+		return nil, nil
+	}
+
+	r, err := azure.ParseResourceID(os.Getenv("BILLING_E2E_STORAGE_ACCOUNT_ID"))
+	if err != nil {
+		return nil, err
+	}
+
+	localFPAuthorizer, err := env.FPAuthorizer(env.TenantID(), env.Environment().ResourceManagerScope)
+	if err != nil {
+		return nil, err
+	}
+
+	e2estorage := storage.NewAccountsClient(env.Environment(), r.SubscriptionID, localFPAuthorizer)
+
+	keys, err := e2estorage.ListKeys(context.Background(), r.ResourceGroup, r.ResourceName, "")
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := azstorage.NewBasicClient(r.ResourceName, *(*keys.Keys)[0].Value)
+	if err != nil {
+		return nil, err
+	}
+
+	return &client, nil
+}
+
 func (m *manager) Ensure(ctx context.Context, doc *api.OpenShiftClusterDocument, sub *api.SubscriptionDocument) error {
-	_, err := m.billingDB.Create(ctx, &api.BillingDocument{
+	billingDoc, err := m.billingDB.Create(ctx, &api.BillingDocument{
 		ID:                        doc.ID,
 		Key:                       doc.Key,
 		ClusterResourceGroupIDKey: doc.ClusterResourceGroupIDKey,
@@ -52,12 +104,16 @@ func (m *manager) Ensure(ctx context.Context, doc *api.OpenShiftClusterDocument,
 		return err
 	}
 
+	if e2eErr := m.createOrUpdateE2EBlob(ctx, billingDoc); e2eErr != nil {
+		m.log.Warnf("createOrUpdateE2EBlob failed: %s", e2eErr)
+	}
+
 	return nil
 }
 
 func (m *manager) Delete(ctx context.Context, doc *api.OpenShiftClusterDocument) error {
 	m.log.Printf("updating billing record with deletion time")
-	_, err := m.billingDB.MarkForDeletion(ctx, doc.ID)
+	billingDoc, err := m.billingDB.MarkForDeletion(ctx, doc.ID)
 	if cosmosdb.IsErrorStatusCode(err, http.StatusNotFound) {
 		return nil
 	}
@@ -65,5 +121,67 @@ func (m *manager) Delete(ctx context.Context, doc *api.OpenShiftClusterDocument)
 		return err
 	}
 
+	if e2eErr := m.createOrUpdateE2EBlob(ctx, billingDoc); e2eErr != nil {
+		// We are not failing the operation if we cannot write to e2e storage account, just warning
+		m.log.Warnf("createOrUpdateE2EBlob failed: %s", e2eErr)
+	}
+
 	return nil
 }
+
+// isSubscriptionRegisteredForE2E returns true if the subscription has the
+// "Microsoft.RedHatOpenShift/SaveAROTestConfig" feature registered
+func isSubscriptionRegisteredForE2E(sub *api.SubscriptionProperties) bool {
+	if sub.TenantID == tenantIDMSFT || sub.TenantID == tenantIDAME {
+		return feature.IsRegisteredForFeature(sub, api.FeatureFlagSaveAROTestConfig)
+	}
+	return false
+}
+
+// createOrUpdateE2Eblob create a copy of the billing document in the e2e
+// storage account. This is used later on by the billing e2e
+func (m *manager) createOrUpdateE2EBlob(ctx context.Context, doc *api.BillingDocument) error {
+	//skip updating the storage account if this is a dev scenario
+	if m.storageClient == nil {
+		return nil
+	}
+
+	// Validate if E2E Feature is registered
+	resource, err := azure.ParseResourceID(doc.Key)
+	if err != nil {
+		return err
+	}
+
+	subscriptionDoc, err := m.subDB.Get(ctx, resource.SubscriptionID)
+	if err != nil {
+		return err
+	}
+
+	if !isSubscriptionRegisteredForE2E(subscriptionDoc.Subscription.Properties) {
+		return nil
+	}
+
+	blobclient := m.storageClient.GetBlobService()
+
+	containerName := strings.ToLower("bill-" + doc.Billing.Location + "-" + resource.ResourceGroup + "-" + resource.ResourceName)
+	if len(containerName) > 63 {
+		containerName = containerName[:63]
+	}
+
+	// The following is added to get rid of the '-' at the end in order to avoid an invalid container name.
+	containerName = strings.TrimSuffix(containerName, "-")
+
+	containerRef := blobclient.GetContainerReference(containerName)
+	_, err = containerRef.CreateIfNotExists(nil)
+	if err != nil {
+		return err
+	}
+
+	blobRef := containerRef.GetBlobReference("billingentity")
+	b, err := json.Marshal(doc)
+	if err != nil {
+		return err
+	}
+
+	return blobRef.CreateBlockBlobFromReader(bytes.NewReader(b), nil)
+}
diff --git a/pkg/util/cluster/cluster.go b/pkg/util/cluster/cluster.go
index 6a327deaf..a25537fa6 100644
--- a/pkg/util/cluster/cluster.go
+++ b/pkg/util/cluster/cluster.go
@@ -421,6 +421,10 @@ func (c *Cluster) createCluster(ctx context.Context, vnetResourceGroup, clusterN
 				FipsValidatedModules: api.FipsValidatedModulesEnabled,
 				Version:              osClusterVersion,
 			},
+			ServicePrincipalProfile: api.ServicePrincipalProfile{
+				ClientID:     clientID,
+				ClientSecret: api.SecureString(clientSecret),
+			},
 			NetworkProfile: api.NetworkProfile{
 				PodCIDR:                "10.128.0.0/14",
 				ServiceCIDR:            "172.30.0.0/16",
@@ -456,13 +460,6 @@ func (c *Cluster) createCluster(ctx context.Context, vnetResourceGroup, clusterN
 		Location: c.env.Location(),
 	}
 
-	if clientID != "" && clientSecret != "" {
-		oc.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
-			ClientID:     clientID,
-			ClientSecret: api.SecureString(clientSecret),
-		}
-	}
-
 	if c.env.IsLocalDevelopmentMode() {
 		err := c.registerSubscription(ctx)
 		if err != nil {
diff --git a/pkg/util/generics/helpers.go b/pkg/util/generics/helpers.go
deleted file mode 100644
index 58e4df68a..000000000
--- a/pkg/util/generics/helpers.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package generics
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-func ConcatMultipleSlices[T any](slices ...[]T) []T {
-	result := []T{}
-
-	for _, s := range slices {
-		result = append(result, s...)
-	}
-
-	return result
-}


Test patch:
diff --git a/Dockerfile.aro-e2e b/Dockerfile.aro-e2e
index da17fb2db..23b575fe1 100644
--- a/Dockerfile.aro-e2e
+++ b/Dockerfile.aro-e2e
@@ -1,7 +1,7 @@
 # Uses a multi-stage container build to build the RP & E2E components.
 #
 ARG REGISTRY
-FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder
+FROM ${REGISTRY}/ubi8/go-toolset:1.20.10 AS builder
 
 USER root
 ENV GOPATH=/root/go
diff --git a/pkg/api/admin/openshiftcluster_validatestatic_test.go b/pkg/api/admin/openshiftcluster_validatestatic_test.go
index 3088f319a..77a4c0d70 100644
--- a/pkg/api/admin/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/admin/openshiftcluster_validatestatic_test.go
@@ -314,7 +314,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 			oc: func() *OpenShiftCluster {
 				return &OpenShiftCluster{
 					Properties: OpenShiftClusterProperties{
-						ServicePrincipalProfile: &ServicePrincipalProfile{
+						ServicePrincipalProfile: ServicePrincipalProfile{
 							ClientID: "clientId",
 						},
 					},
@@ -330,7 +330,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 			oc: func() *OpenShiftCluster {
 				return &OpenShiftCluster{
 					Properties: OpenShiftClusterProperties{
-						ServicePrincipalProfile: &ServicePrincipalProfile{
+						ServicePrincipalProfile: ServicePrincipalProfile{
 							SPObjectID: "clientId",
 						},
 					},
diff --git a/pkg/api/admin/openshiftversion_validatestatic.go b/pkg/api/admin/openshiftversion_validatestatic.go
index 63fee7ebc..83fb14215 100644
--- a/pkg/api/admin/openshiftversion_validatestatic.go
+++ b/pkg/api/admin/openshiftversion_validatestatic.go
@@ -21,7 +21,7 @@ func (sv openShiftVersionStaticValidator) Static(_new interface{}, _current *api
 		current = (&openShiftVersionConverter{}).ToExternal(_current).(*OpenShiftVersion)
 	}
 
-	err := sv.validate(new)
+	err := sv.validate(new, current == nil)
 	if err != nil {
 		return err
 	}
@@ -33,7 +33,7 @@ func (sv openShiftVersionStaticValidator) Static(_new interface{}, _current *api
 	return sv.validateDelta(new, current)
 }
 
-func (sv openShiftVersionStaticValidator) validate(new *OpenShiftVersion) error {
+func (sv openShiftVersionStaticValidator) validate(new *OpenShiftVersion, isCreate bool) error {
 	if new.Properties.Version == "" {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.version", "Must be provided")
 	}
diff --git a/pkg/api/v20191231preview/openshiftcluster_validatestatic.go b/pkg/api/v20191231preview/openshiftcluster_validatestatic.go
index 8e34729ed..84ceba628 100644
--- a/pkg/api/v20191231preview/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20191231preview/openshiftcluster_validatestatic.go
@@ -91,7 +91,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile); err != nil {
@@ -172,10 +172,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go b/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go
index 4bac8bc1d..85ce86574 100644
--- a/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go
@@ -56,7 +56,7 @@ func validOpenShiftCluster() *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -387,13 +387,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20200430/openshiftcluster_validatestatic.go b/pkg/api/v20200430/openshiftcluster_validatestatic.go
index 6b056f8b8..f7212a4b7 100644
--- a/pkg/api/v20200430/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20200430/openshiftcluster_validatestatic.go
@@ -92,7 +92,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile); err != nil {
@@ -175,10 +175,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20200430/openshiftcluster_validatestatic_test.go b/pkg/api/v20200430/openshiftcluster_validatestatic_test.go
index a483dadd8..9ae089e40 100644
--- a/pkg/api/v20200430/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20200430/openshiftcluster_validatestatic_test.go
@@ -56,7 +56,7 @@ func validOpenShiftCluster() *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -387,13 +387,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20210901preview/openshiftcluster_validatestatic.go b/pkg/api/v20210901preview/openshiftcluster_validatestatic.go
index 16527c82c..3735f46b8 100644
--- a/pkg/api/v20210901preview/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20210901preview/openshiftcluster_validatestatic.go
@@ -92,7 +92,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile); err != nil {
@@ -175,10 +175,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go b/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go
index b423c48b6..e1614ed13 100644
--- a/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go
@@ -70,7 +70,7 @@ func validOpenShiftCluster() *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -404,13 +404,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20220401/openshiftcluster_validatestatic.go b/pkg/api/v20220401/openshiftcluster_validatestatic.go
index 7c63e28c2..4f4068750 100644
--- a/pkg/api/v20220401/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20220401/openshiftcluster_validatestatic.go
@@ -92,7 +92,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile); err != nil {
@@ -181,10 +181,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20220401/openshiftcluster_validatestatic_test.go b/pkg/api/v20220401/openshiftcluster_validatestatic_test.go
index ef0e6075d..31c7c4550 100644
--- a/pkg/api/v20220401/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20220401/openshiftcluster_validatestatic_test.go
@@ -71,7 +71,7 @@ func validOpenShiftCluster() *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -418,13 +418,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20220904/openshiftcluster_validatestatic.go b/pkg/api/v20220904/openshiftcluster_validatestatic.go
index 7888c0a4c..02df81a0e 100644
--- a/pkg/api/v20220904/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20220904/openshiftcluster_validatestatic.go
@@ -92,7 +92,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile); err != nil {
@@ -181,10 +181,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20220904/openshiftcluster_validatestatic_test.go b/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
index 6f882697a..f22336d52 100644
--- a/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
@@ -77,7 +77,7 @@ func validOpenShiftCluster(name, location string) *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -433,13 +433,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20230401/openshiftcluster_validatestatic.go b/pkg/api/v20230401/openshiftcluster_validatestatic.go
index 155ec1526..28b628f05 100644
--- a/pkg/api/v20230401/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20230401/openshiftcluster_validatestatic.go
@@ -92,7 +92,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile, p.APIServerProfile.Visibility, p.IngressProfiles[0].Visibility); err != nil {
@@ -181,10 +181,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20230401/openshiftcluster_validatestatic_test.go b/pkg/api/v20230401/openshiftcluster_validatestatic_test.go
index 252f18637..e46454528 100644
--- a/pkg/api/v20230401/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20230401/openshiftcluster_validatestatic_test.go
@@ -77,7 +77,7 @@ func validOpenShiftCluster(name, location string) *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -434,13 +434,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20230701preview/openshiftcluster_validatestatic.go b/pkg/api/v20230701preview/openshiftcluster_validatestatic.go
index b478847f1..74b39283c 100644
--- a/pkg/api/v20230701preview/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20230701preview/openshiftcluster_validatestatic.go
@@ -95,7 +95,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile, p.APIServerProfile.Visibility, p.IngressProfiles[0].Visibility); err != nil {
@@ -187,10 +187,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20230701preview/openshiftcluster_validatestatic_test.go b/pkg/api/v20230701preview/openshiftcluster_validatestatic_test.go
index 9270983cf..d73e1b77a 100644
--- a/pkg/api/v20230701preview/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20230701preview/openshiftcluster_validatestatic_test.go
@@ -79,7 +79,7 @@ func validOpenShiftCluster(name, location string) *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -447,13 +447,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20230904/openshiftcluster_validatestatic.go b/pkg/api/v20230904/openshiftcluster_validatestatic.go
index 1d26aa704..bbb480036 100644
--- a/pkg/api/v20230904/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20230904/openshiftcluster_validatestatic.go
@@ -92,7 +92,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile, p.APIServerProfile.Visibility, p.IngressProfiles[0].Visibility); err != nil {
@@ -185,10 +185,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20230904/openshiftcluster_validatestatic_test.go b/pkg/api/v20230904/openshiftcluster_validatestatic_test.go
index 1f746a3b9..b0852939b 100644
--- a/pkg/api/v20230904/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20230904/openshiftcluster_validatestatic_test.go
@@ -77,7 +77,7 @@ func validOpenShiftCluster(name, location string) *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -450,13 +450,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20231122/openshiftcluster_validatestatic.go b/pkg/api/v20231122/openshiftcluster_validatestatic.go
index 9fcf75166..c310909c9 100644
--- a/pkg/api/v20231122/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20231122/openshiftcluster_validatestatic.go
@@ -95,7 +95,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile, p.APIServerProfile.Visibility, p.IngressProfiles[0].Visibility); err != nil {
@@ -191,10 +191,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".servicePrincipalProfile", "ServicePrincipalProfile cannot be nil in this API version.")
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20231122/openshiftcluster_validatestatic_test.go b/pkg/api/v20231122/openshiftcluster_validatestatic_test.go
index 2d46f05d4..6a03d7518 100644
--- a/pkg/api/v20231122/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20231122/openshiftcluster_validatestatic_test.go
@@ -87,7 +87,7 @@ func validOpenShiftCluster(name, location string) *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
@@ -478,13 +478,6 @@ func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
 			},
 			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
 		},
-		{
-			name: "nil ServicePrincipalProfile invalid",
-			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = nil
-			},
-			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.servicePrincipalProfile: ServicePrincipalProfile cannot be nil in this API version.",
-		},
 	}
 
 	runTests(t, testModeCreate, tests)
diff --git a/pkg/api/v20240812preview/openshiftcluster_validatestatic.go b/pkg/api/v20240812preview/openshiftcluster_validatestatic.go
index 43550d710..8896cd106 100644
--- a/pkg/api/v20240812preview/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20240812preview/openshiftcluster_validatestatic.go
@@ -95,7 +95,7 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
 	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
 		return err
 	}
-	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", p.ServicePrincipalProfile); err != nil {
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
 		return err
 	}
 	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile, p.APIServerProfile.Visibility, p.IngressProfiles[0].Visibility); err != nil {
@@ -191,10 +191,6 @@ func (sv openShiftClusterStaticValidator) validateConsoleProfile(path string, cp
 }
 
 func (sv openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	if spp == nil {
-		return nil
-	}
-
 	valid := uuid.IsValid(spp.ClientID)
 	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
diff --git a/pkg/api/v20240812preview/openshiftcluster_validatestatic_test.go b/pkg/api/v20240812preview/openshiftcluster_validatestatic_test.go
index 9e7e4d26d..949bc60b4 100644
--- a/pkg/api/v20240812preview/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20240812preview/openshiftcluster_validatestatic_test.go
@@ -87,7 +87,7 @@ func validOpenShiftCluster(name, location string) *OpenShiftCluster {
 			ConsoleProfile: ConsoleProfile{
 				URL: "",
 			},
-			ServicePrincipalProfile: &ServicePrincipalProfile{
+			ServicePrincipalProfile: ServicePrincipalProfile{
 				ClientSecret: "clientSecret",
 				ClientID:     "11111111-1111-1111-1111-111111111111",
 			},
diff --git a/pkg/cluster/adminupdate_test.go b/pkg/cluster/adminupdate_test.go
index 5804ad114..2a3473ba3 100644
--- a/pkg/cluster/adminupdate_test.go
+++ b/pkg/cluster/adminupdate_test.go
@@ -10,7 +10,6 @@ import (
 	"github.com/go-test/deep"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	utilgenerics "github.com/Azure/ARO-RP/pkg/util/generics"
 )
 
 func TestAdminUpdateSteps(t *testing.T) {
@@ -32,65 +31,9 @@ func TestAdminUpdateSteps(t *testing.T) {
 		}
 	}
 
-	zerothSteps := []string{
-		"[Action initializeKubernetesClients-fm]",
-		"[Action ensureBillingRecord-fm]",
-		"[Action ensureDefaults-fm]",
-		"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
-		"[Action fixInfraID-fm]",
-	}
-
-	generalFixesSteps := []string{
-		"[Action ensureResourceGroup-fm]",
-		"[Action createOrUpdateDenyAssignment-fm]",
-		"[Action ensureServiceEndpoints-fm]",
-		"[Action populateRegistryStorageAccountName-fm]",
-		"[Action migrateStorageAccounts-fm]",
-		"[Action fixSSH-fm]",
-		"[Action startVMs-fm]",
-		"[Condition apiServersReady-fm, timeout 30m0s]",
-		"[Action fixSREKubeconfig-fm]",
-		"[Action fixUserAdminKubeconfig-fm]",
-		"[Action createOrUpdateRouterIPFromCluster-fm]",
-		"[Action ensureGatewayUpgrade-fm]",
-		"[Action rotateACRTokenPassword-fm]",
-		"[Action populateRegistryStorageAccountName-fm]",
-		"[Action ensureMTUSize-fm]",
-	}
-
-	certificateRenewalSteps := []string{
-		"[Action startVMs-fm]",
-		"[Condition apiServersReady-fm, timeout 30m0s]",
-		"[Action populateDatabaseIntIP-fm]",
-		"[Action fixMCSCert-fm]",
-		"[Action fixMCSUserData-fm]",
-		"[Action configureAPIServerCertificate-fm]",
-		"[Action configureIngressCertificate-fm]",
-		"[Action initializeOperatorDeployer-fm]",
-		"[Action renewMDSDCertificate-fm]",
-	}
-
-	operatorUpdateSteps := []string{
-		"[Action startVMs-fm]",
-		"[Condition apiServersReady-fm, timeout 30m0s]",
-		"[Action initializeOperatorDeployer-fm]",
-		"[Action ensureAROOperator-fm]",
-		"[Condition aroDeploymentReady-fm, timeout 20m0s]",
-		"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
-	}
-
-	hiveSteps := []string{
-		"[Action hiveCreateNamespace-fm]",
-		"[Action hiveEnsureResources-fm]",
-		"[Condition hiveClusterDeploymentReady-fm, timeout 5m0s]",
-		"[Action hiveResetCorrelationData-fm]",
-	}
-
-	updateProvisionedBySteps := []string{"[Action updateProvisionedBy-fm]"}
-
 	for _, tt := range []struct {
 		name           string
-		fixture        func() (doc *api.OpenShiftClusterDocument, adoptHive bool)
+		fixture        func() (*api.OpenShiftClusterDocument, bool)
 		shouldRunSteps []string
 	}{
 		{
@@ -101,7 +44,19 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskOperator
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(zerothSteps, operatorUpdateSteps),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+			},
 		},
 		{
 			name: "ARO Operator Update on <= 4.6 cluster does not update operator",
@@ -112,7 +67,16 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.ClusterProfile.Version = "4.6.62"
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(zerothSteps),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action initializeOperatorDeployer-fm]",
+			},
 		},
 		{
 			name: "ARO Operator Update on 4.7.0 cluster does update operator",
@@ -123,20 +87,64 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.ClusterProfile.Version = "4.7.0"
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(zerothSteps, operatorUpdateSteps),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+			},
 		},
 		{
-			name: "Everything update and adopt Hive.",
+			name: "Everything update",
 			fixture: func() (*api.OpenShiftClusterDocument, bool) {
 				doc := baseClusterDoc()
 				doc.OpenShiftCluster.Properties.ProvisioningState = api.ProvisioningStateAdminUpdating
 				doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskEverything
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(
-				zerothSteps, generalFixesSteps, certificateRenewalSteps,
-				operatorUpdateSteps, hiveSteps, updateProvisionedBySteps,
-			),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action ensureResourceGroup-fm]",
+				"[Action createOrUpdateDenyAssignment-fm]",
+				"[Action ensureServiceEndpoints-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action migrateStorageAccounts-fm]",
+				"[Action fixSSH-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixSREKubeconfig-fm]",
+				"[Action fixUserAdminKubeconfig-fm]",
+				"[Action createOrUpdateRouterIPFromCluster-fm]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action ensureGatewayUpgrade-fm]",
+				"[Action rotateACRTokenPassword-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action ensureMTUSize-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+				"[Action hiveCreateNamespace-fm]",
+				"[Action hiveEnsureResources-fm]",
+				"[Condition hiveClusterDeploymentReady-fm, timeout 5m0s]",
+				"[Action hiveResetCorrelationData-fm]",
+				"[Action updateProvisionedBy-fm]",
+			},
 		},
 		{
 			name: "Everything update on <= 4.6 cluster does not update operator",
@@ -147,10 +155,39 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.ClusterProfile.Version = "4.6.62"
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(
-				zerothSteps, generalFixesSteps, certificateRenewalSteps,
-				hiveSteps, updateProvisionedBySteps,
-			),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action ensureResourceGroup-fm]",
+				"[Action createOrUpdateDenyAssignment-fm]",
+				"[Action ensureServiceEndpoints-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action migrateStorageAccounts-fm]",
+				"[Action fixSSH-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixSREKubeconfig-fm]",
+				"[Action fixUserAdminKubeconfig-fm]",
+				"[Action createOrUpdateRouterIPFromCluster-fm]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action ensureGatewayUpgrade-fm]",
+				"[Action rotateACRTokenPassword-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action ensureMTUSize-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action hiveCreateNamespace-fm]",
+				"[Action hiveEnsureResources-fm]",
+				"[Condition hiveClusterDeploymentReady-fm, timeout 5m0s]",
+				"[Action hiveResetCorrelationData-fm]",
+				"[Action updateProvisionedBy-fm]",
+			},
 		},
 		{
 			name: "Blank, Hive not adopting (should perform everything but Hive)",
@@ -160,23 +197,83 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskEverything
 				return doc, false
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(
-				zerothSteps, generalFixesSteps, certificateRenewalSteps,
-				operatorUpdateSteps, updateProvisionedBySteps,
-			),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action ensureResourceGroup-fm]",
+				"[Action createOrUpdateDenyAssignment-fm]",
+				"[Action ensureServiceEndpoints-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action migrateStorageAccounts-fm]",
+				"[Action fixSSH-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixSREKubeconfig-fm]",
+				"[Action fixUserAdminKubeconfig-fm]",
+				"[Action createOrUpdateRouterIPFromCluster-fm]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action ensureGatewayUpgrade-fm]",
+				"[Action rotateACRTokenPassword-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action ensureMTUSize-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+				"[Action updateProvisionedBy-fm]",
+			},
 		},
 		{
-			name: "Blank, Hive adopting (should perform everything)",
+			name: "Blank, Hive performing adopting (should perform everything but Hive)",
 			fixture: func() (*api.OpenShiftClusterDocument, bool) {
 				doc := baseClusterDoc()
 				doc.OpenShiftCluster.Properties.ProvisioningState = api.ProvisioningStateAdminUpdating
 				doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskEverything
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(
-				zerothSteps, generalFixesSteps, certificateRenewalSteps,
-				operatorUpdateSteps, hiveSteps, updateProvisionedBySteps,
-			),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action ensureResourceGroup-fm]",
+				"[Action createOrUpdateDenyAssignment-fm]",
+				"[Action ensureServiceEndpoints-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action migrateStorageAccounts-fm]",
+				"[Action fixSSH-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixSREKubeconfig-fm]",
+				"[Action fixUserAdminKubeconfig-fm]",
+				"[Action createOrUpdateRouterIPFromCluster-fm]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action ensureGatewayUpgrade-fm]",
+				"[Action rotateACRTokenPassword-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action ensureMTUSize-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+				"[Action hiveCreateNamespace-fm]",
+				"[Action hiveEnsureResources-fm]",
+				"[Condition hiveClusterDeploymentReady-fm, timeout 5m0s]",
+				"[Action hiveResetCorrelationData-fm]",
+				"[Action updateProvisionedBy-fm]",
+			},
 		},
 		{
 			name: "Rotate in-cluster MDSD/Ingress/API certs",
@@ -186,7 +283,22 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskRenewCerts
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(zerothSteps, certificateRenewalSteps),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action renewMDSDCertificate-fm]",
+			},
 		},
 		{
 			name: "adminUpdate() does not adopt Hive-created clusters",
@@ -198,10 +310,38 @@ func TestAdminUpdateSteps(t *testing.T) {
 				doc.OpenShiftCluster.Properties.HiveProfile.CreatedByHive = true
 				return doc, true
 			},
-			shouldRunSteps: utilgenerics.ConcatMultipleSlices(
-				zerothSteps, generalFixesSteps, certificateRenewalSteps,
-				operatorUpdateSteps, updateProvisionedBySteps,
-			),
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[AuthorizationRetryingAction fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[Action ensureResourceGroup-fm]",
+				"[Action createOrUpdateDenyAssignment-fm]",
+				"[Action ensureServiceEndpoints-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action migrateStorageAccounts-fm]",
+				"[Action fixSSH-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixSREKubeconfig-fm]",
+				"[Action fixUserAdminKubeconfig-fm]",
+				"[Action createOrUpdateRouterIPFromCluster-fm]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action ensureGatewayUpgrade-fm]",
+				"[Action rotateACRTokenPassword-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action ensureMTUSize-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+				"[Action updateProvisionedBy-fm]",
+			},
 		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/cluster/clusterserviceprincipal_test.go b/pkg/cluster/clusterserviceprincipal_test.go
index 0f85431fd..f160ab67d 100644
--- a/pkg/cluster/clusterserviceprincipal_test.go
+++ b/pkg/cluster/clusterserviceprincipal_test.go
@@ -50,7 +50,7 @@ func TestCreateOrUpdateClusterServicePrincipalRBAC(t *testing.T) {
 					ClusterProfile: api.ClusterProfile{
 						ResourceGroupID: resourceGroupID,
 					},
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						SPObjectID: fakeClusterSPObjectId,
 					},
 				},
@@ -263,7 +263,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 	for _, tt := range []struct {
 		name          string
 		kubernetescli func() *fake.Clientset
-		spp           *api.ServicePrincipalProfile
+		spp           api.ServicePrincipalProfile
 		wantSecret    func() *corev1.Secret
 		wantErrMsg    string
 	}{
@@ -272,7 +272,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 			kubernetescli: func() *fake.Clientset {
 				return fake.NewSimpleClientset()
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -287,7 +287,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 				})
 				return cli
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -300,7 +300,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 				secret.Data["cloud-config"] = []byte("This is some random data that is not going to unmarshal properly!")
 				return fake.NewSimpleClientset(&secret)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -312,7 +312,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 				secret := getFakeAROSecret("aadClientId", "aadClientSecret")
 				return fake.NewSimpleClientset(&secret)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientIdNew",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -327,7 +327,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 				secret := getFakeAROSecret("aadClientId", "aadClientSecret")
 				return fake.NewSimpleClientset(&secret)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -342,7 +342,7 @@ func TestServicePrincipalUpdated(t *testing.T) {
 				secret := getFakeAROSecret("aadClientId", "aadClientSecret")
 				return fake.NewSimpleClientset(&secret)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecret",
 			},
@@ -397,7 +397,7 @@ func TestUpdateAROSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "aadClientId",
 						ClientSecret: "aadClientSecret",
 					},
@@ -418,7 +418,7 @@ func TestUpdateAROSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "new-client-id",
 						ClientSecret: "aadClientSecret",
 					},
@@ -449,7 +449,7 @@ func TestUpdateAROSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "clientID",
 						ClientSecret: "aadClientSecret",
 					},
@@ -528,7 +528,7 @@ func TestUpdateOpenShiftSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "azure_client_id_value",
 						ClientSecret: "azure_client_secret_value",
 					},
@@ -553,7 +553,7 @@ func TestUpdateOpenShiftSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "azure_client_id_value",
 						ClientSecret: "new_azure_client_secret_value",
 					},
@@ -580,7 +580,7 @@ func TestUpdateOpenShiftSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "azure_client_id_value",
 						ClientSecret: "azure_client_secret_value",
 					},
@@ -606,7 +606,7 @@ func TestUpdateOpenShiftSecret(t *testing.T) {
 			},
 			doc: api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "azure_client_id_value",
 						ClientSecret: "azure_client_secret_value",
 					},
diff --git a/pkg/cluster/condition_test.go b/pkg/cluster/condition_test.go
index a05156592..74ded8740 100644
--- a/pkg/cluster/condition_test.go
+++ b/pkg/cluster/condition_test.go
@@ -248,7 +248,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 		name          string
 		kubernetescli func() *fake.Clientset
 		dynamiccli    func() *dynamicfake.FakeDynamicClient
-		spp           *api.ServicePrincipalProfile
+		spp           api.ServicePrincipalProfile
 		want          bool
 		wantErrMsg    string
 	}{
@@ -261,7 +261,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 			dynamiccli: func() *dynamicfake.FakeDynamicClient {
 				return dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecret",
 			},
@@ -276,7 +276,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 			dynamiccli: func() *dynamicfake.FakeDynamicClient {
 				return dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -295,7 +295,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 				})
 				return dynamiccli
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -317,7 +317,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 				}
 				return dynamicfake.NewSimpleDynamicClient(scheme.Scheme, &cr)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -343,7 +343,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 				}
 				return dynamicfake.NewSimpleDynamicClient(scheme.Scheme, &cr)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
@@ -368,7 +368,7 @@ func TestAroCredentialsRequestReconciled(t *testing.T) {
 				}
 				return dynamicfake.NewSimpleDynamicClient(scheme.Scheme, &cr)
 			},
-			spp: &api.ServicePrincipalProfile{
+			spp: api.ServicePrincipalProfile{
 				ClientID:     "aadClientId",
 				ClientSecret: "aadClientSecretNew",
 			},
diff --git a/pkg/cluster/denyassignment_test.go b/pkg/cluster/denyassignment_test.go
index e9316ca47..77a53d83e 100644
--- a/pkg/cluster/denyassignment_test.go
+++ b/pkg/cluster/denyassignment_test.go
@@ -30,7 +30,7 @@ func TestCreateOrUpdateDenyAssignment(t *testing.T) {
 					ClusterProfile: api.ClusterProfile{
 						ResourceGroupID: fmt.Sprintf("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/%s", clusterRGName),
 					},
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						SPObjectID: fakeClusterSPObjectId,
 					},
 				},
diff --git a/pkg/containerinstall/install_test.go b/pkg/containerinstall/install_test.go
index 12a075ab5..620a7e62f 100644
--- a/pkg/containerinstall/install_test.go
+++ b/pkg/containerinstall/install_test.go
@@ -27,7 +27,7 @@ import (
 	testlog "github.com/Azure/ARO-RP/test/util/log"
 )
 
-const TEST_PULLSPEC = "registry.access.redhat.com/ubi8/go-toolset:1.20.12-5"
+const TEST_PULLSPEC = "registry.access.redhat.com/ubi8/go-toolset:1.20.10"
 
 var _ = Describe("Podman", Ordered, func() {
 	var err error
diff --git a/pkg/frontend/admin_openshiftcluster_list_test.go b/pkg/frontend/admin_openshiftcluster_list_test.go
index c39934e30..1e7d7da88 100644
--- a/pkg/frontend/admin_openshiftcluster_list_test.go
+++ b/pkg/frontend/admin_openshiftcluster_list_test.go
@@ -49,7 +49,7 @@ func TestAdminListOpenShiftCluster(t *testing.T) {
 								ClusterProfile: api.ClusterProfile{
 									PullSecret: "{}",
 								},
-								ServicePrincipalProfile: &api.ServicePrincipalProfile{
+								ServicePrincipalProfile: api.ServicePrincipalProfile{
 									ClientSecret: "clientSecret1",
 								},
 							},
@@ -65,7 +65,7 @@ func TestAdminListOpenShiftCluster(t *testing.T) {
 								ClusterProfile: api.ClusterProfile{
 									PullSecret: "{}",
 								},
-								ServicePrincipalProfile: &api.ServicePrincipalProfile{
+								ServicePrincipalProfile: api.ServicePrincipalProfile{
 									ClientSecret: "clientSecret2",
 								},
 							},
@@ -80,17 +80,11 @@ func TestAdminListOpenShiftCluster(t *testing.T) {
 						ID:   testdatabase.GetResourcePath(mockSubID, "resourceName1"),
 						Name: "resourceName1",
 						Type: "Microsoft.RedHatOpenShift/openshiftClusters",
-						Properties: admin.OpenShiftClusterProperties{
-							ServicePrincipalProfile: &admin.ServicePrincipalProfile{},
-						},
 					},
 					{
 						ID:   testdatabase.GetResourcePath(otherMockSubID, "resourceName2"),
 						Name: "resourceName2",
 						Type: "Microsoft.RedHatOpenShift/openshiftClusters",
-						Properties: admin.OpenShiftClusterProperties{
-							ServicePrincipalProfile: &admin.ServicePrincipalProfile{},
-						},
 					},
 				},
 			},
diff --git a/pkg/frontend/asyncoperationresult_get_test.go b/pkg/frontend/asyncoperationresult_get_test.go
index 516fcac90..2e56de15d 100644
--- a/pkg/frontend/asyncoperationresult_get_test.go
+++ b/pkg/frontend/asyncoperationresult_get_test.go
@@ -49,7 +49,7 @@ func TestGetAsyncOperationResult(t *testing.T) {
 							ClusterProfile: api.ClusterProfile{
 								PullSecret: "{}",
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 						},
@@ -69,9 +69,6 @@ func TestGetAsyncOperationResult(t *testing.T) {
 				ID:   testdatabase.GetResourcePath(mockSubID, "fakeClusterID"),
 				Name: "resourceName",
 				Type: "Microsoft.RedHatOpenShift/openshiftClusters",
-				Properties: v20200430.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
-				},
 			},
 		},
 		{
diff --git a/pkg/frontend/openshiftcluster_get_test.go b/pkg/frontend/openshiftcluster_get_test.go
index 65fff1687..91a3988f3 100644
--- a/pkg/frontend/openshiftcluster_get_test.go
+++ b/pkg/frontend/openshiftcluster_get_test.go
@@ -47,7 +47,7 @@ func TestGetOpenShiftCluster(t *testing.T) {
 							ClusterProfile: api.ClusterProfile{
 								PullSecret: "{}",
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 						},
@@ -62,9 +62,6 @@ func TestGetOpenShiftCluster(t *testing.T) {
 					ID:   tt.resourceID,
 					Name: "resourceName",
 					Type: "Microsoft.RedHatOpenShift/openshiftClusters",
-					Properties: v20200430.OpenShiftClusterProperties{
-						ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
-					},
 				}
 			},
 		},
diff --git a/pkg/frontend/openshiftcluster_list_test.go b/pkg/frontend/openshiftcluster_list_test.go
index c22c16c99..fc6e0678f 100644
--- a/pkg/frontend/openshiftcluster_list_test.go
+++ b/pkg/frontend/openshiftcluster_list_test.go
@@ -32,7 +32,7 @@ func makeDoc(num int) *api.OpenShiftClusterDocument {
 				ClusterProfile: api.ClusterProfile{
 					PullSecret: "{}",
 				},
-				ServicePrincipalProfile: &api.ServicePrincipalProfile{
+				ServicePrincipalProfile: api.ServicePrincipalProfile{
 					ClientSecret: (api.SecureString)(clientSecret),
 				},
 			},
@@ -78,17 +78,11 @@ func TestListOpenShiftCluster(t *testing.T) {
 							ID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName01", mockSubID),
 							Name: "resourceName01",
 							Type: "Microsoft.RedHatOpenShift/openShiftClusters",
-							Properties: v20200430.OpenShiftClusterProperties{
-								ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
-							},
 						},
 						{
 							ID:   testdatabase.GetResourcePath(mockSubID, "resourceName02"),
 							Name: "resourceName02",
 							Type: "Microsoft.RedHatOpenShift/openShiftClusters",
-							Properties: v20200430.OpenShiftClusterProperties{
-								ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
-							},
 						},
 					},
 				}
@@ -123,9 +117,6 @@ func TestListOpenShiftCluster(t *testing.T) {
 						ID:   testdatabase.GetResourcePath(mockSubID, fmt.Sprintf("resourceName%02d", i)),
 						Name: fmt.Sprintf("resourceName%02d", i),
 						Type: "Microsoft.RedHatOpenShift/openShiftClusters",
-						Properties: v20200430.OpenShiftClusterProperties{
-							ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
-						},
 					})
 				}
 
@@ -154,9 +145,6 @@ func TestListOpenShiftCluster(t *testing.T) {
 							ID:   testdatabase.GetResourcePath(mockSubID, "resourceName11"),
 							Name: "resourceName11",
 							Type: "Microsoft.RedHatOpenShift/openShiftClusters",
-							Properties: v20200430.OpenShiftClusterProperties{
-								ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
-							},
 						},
 					},
 				}
diff --git a/pkg/frontend/openshiftcluster_putorpatch_test.go b/pkg/frontend/openshiftcluster_putorpatch_test.go
index f7c10ade9..afb59df38 100644
--- a/pkg/frontend/openshiftcluster_putorpatch_test.go
+++ b/pkg/frontend/openshiftcluster_putorpatch_test.go
@@ -2123,7 +2123,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 							},
 							IngressProfiles: []api.IngressProfile{{Name: "will-be-removed"}},
 							WorkerProfiles:  []api.WorkerProfile{{Name: "will-be-removed"}},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "will-be-kept",
 							},
 							NetworkProfile: api.NetworkProfile{
@@ -2162,7 +2162,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 								Domain:               "changed",
 								FipsValidatedModules: api.FipsValidatedModulesDisabled,
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "will-be-kept",
 							},
 							NetworkProfile: api.NetworkProfile{
@@ -2195,7 +2195,6 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 					ClusterProfile: v20200430.ClusterProfile{
 						Domain: "changed",
 					},
-					ServicePrincipalProfile: &v20200430.ServicePrincipalProfile{},
 				},
 			},
 		},
@@ -2679,9 +2678,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 		{
 			name: "creating cluster failing when provided cluster resource group already contains a cluster",
 			request: func(oc *v20200430.OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = &v20200430.ServicePrincipalProfile{
-					ClientID: mockSubID,
-				}
+				oc.Properties.ServicePrincipalProfile.ClientID = mockSubID
 				oc.Properties.ClusterProfile.ResourceGroupID = fmt.Sprintf("/subscriptions/%s/resourcegroups/aro-vjb21wca", mockSubID)
 			},
 			fixture: func(f *testdatabase.Fixture) {
@@ -2729,9 +2726,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 		{
 			name: "creating cluster failing when provided client ID is not unique",
 			request: func(oc *v20200430.OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile = &v20200430.ServicePrincipalProfile{
-					ClientID: mockSubID,
-				}
+				oc.Properties.ServicePrincipalProfile.ClientID = mockSubID
 			},
 			fixture: func(f *testdatabase.Fixture) {
 				f.AddSubscriptionDocuments(&api.SubscriptionDocument{
@@ -2923,7 +2918,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) {
 							VMSize:           v20220401.VMSize("Standard_D32s_v3"),
 							SubnetID:         fmt.Sprintf("/subscriptions/%s/resourcegroups/network/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master", mockSubID),
 						},
-						ServicePrincipalProfile: &v20220401.ServicePrincipalProfile{
+						ServicePrincipalProfile: v20220401.ServicePrincipalProfile{
 							ClientID:     "00000000-0000-0000-1111-000000000000",
 							ClientSecret: "00000000-0000-0000-0000-000000000000",
 						},
@@ -2978,7 +2973,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) {
 								VMSize:           api.VMSize("Standard_D32s_v3"),
 								SubnetID:         fmt.Sprintf("/subscriptions/%s/resourcegroups/network/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master", mockSubID),
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientID:     "00000000-0000-0000-1111-000000000000",
 								ClientSecret: "00000000-0000-0000-0000-000000000000",
 							},
@@ -3037,7 +3032,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) {
 								VMSize:           api.VMSize("Standard_D32s_v3"),
 								SubnetID:         fmt.Sprintf("/subscriptions/%s/resourcegroups/network/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master", mockSubID),
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientID:     "00000000-0000-0000-1111-000000000000",
 								ClientSecret: "00000000-0000-0000-0000-000000000000",
 							},
@@ -3094,7 +3089,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) {
 						VMSize:           v20220401.VMSize("Standard_D32s_v3"),
 						SubnetID:         fmt.Sprintf("/subscriptions/%s/resourcegroups/network/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master", mockSubID),
 					},
-					ServicePrincipalProfile: &v20220401.ServicePrincipalProfile{
+					ServicePrincipalProfile: v20220401.ServicePrincipalProfile{
 						ClientID: "00000000-0000-0000-1111-000000000000",
 					},
 					NetworkProfile: v20220401.NetworkProfile{
diff --git a/pkg/frontend/openshiftclustercredentials_post_test.go b/pkg/frontend/openshiftclustercredentials_post_test.go
index 54f445928..e70924557 100644
--- a/pkg/frontend/openshiftclustercredentials_post_test.go
+++ b/pkg/frontend/openshiftclustercredentials_post_test.go
@@ -58,7 +58,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
 							ClusterProfile: api.ClusterProfile{
 								PullSecret: "{}",
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 							KubeadminPassword: "password",
@@ -105,7 +105,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
 							ClusterProfile: api.ClusterProfile{
 								PullSecret: "{}",
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 						},
@@ -139,7 +139,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
 							ClusterProfile: api.ClusterProfile{
 								PullSecret: "{}",
 							},
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 						},
@@ -174,7 +174,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
 								PullSecret: "{}",
 							},
 							FailedProvisioningState: api.ProvisioningStateCreating,
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 						},
@@ -209,7 +209,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
 								PullSecret: "{}",
 							},
 							FailedProvisioningState: api.ProvisioningStateDeleting,
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{
+							ServicePrincipalProfile: api.ServicePrincipalProfile{
 								ClientSecret: "clientSecret",
 							},
 						},
diff --git a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go
index c233024ba..e9c476184 100644
--- a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go
+++ b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go
@@ -54,9 +54,8 @@ func TestPostOpenShiftClusterKubeConfigCredentials(t *testing.T) {
 						Name: "resourceName",
 						Type: "Microsoft.RedHatOpenShift/openshiftClusters",
 						Properties: api.OpenShiftClusterProperties{
-							ProvisioningState:       api.ProvisioningStateSucceeded,
-							UserAdminKubeconfig:     api.SecureBytes("{kubeconfig}"),
-							ServicePrincipalProfile: &api.ServicePrincipalProfile{},
+							ProvisioningState:   api.ProvisioningStateSucceeded,
+							UserAdminKubeconfig: api.SecureBytes("{kubeconfig}"),
 						},
 					},
 				})
diff --git a/pkg/util/billing/billing_test.go b/pkg/util/billing/billing_test.go
index dca66e6f9..5cd23eda0 100644
--- a/pkg/util/billing/billing_test.go
+++ b/pkg/util/billing/billing_test.go
@@ -19,6 +19,101 @@ import (
 	utilerror "github.com/Azure/ARO-RP/test/util/error"
 )
 
+func TestIsSubscriptionRegisteredForE2E(t *testing.T) {
+	const tenantID = "11111111-1111-1111-1111-111111111111"
+
+	for _, tt := range []struct {
+		name string
+		sub  api.SubscriptionProperties
+		want bool
+	}{
+		{
+			name: "empty",
+		},
+		{
+			name: "sub without feature flag registered and not internal tenant",
+			sub: api.SubscriptionProperties{
+				TenantID: tenantID,
+				RegisteredFeatures: []api.RegisteredFeatureProfile{
+					{
+						Name:  "RandomFeature",
+						State: "Registered",
+					},
+				},
+			},
+		},
+		{
+			name: "sub with feature flag registered and not internal tenant",
+			sub: api.SubscriptionProperties{
+				TenantID: tenantID,
+				RegisteredFeatures: []api.RegisteredFeatureProfile{
+					{
+						Name:  api.FeatureFlagSaveAROTestConfig,
+						State: "Registered",
+					},
+				},
+			},
+		},
+		{
+			name: "AME internal tenant and feature flag not registered",
+			sub: api.SubscriptionProperties{
+				TenantID: tenantIDAME,
+				RegisteredFeatures: []api.RegisteredFeatureProfile{
+					{
+						Name:  "RandomFeature",
+						State: "Registered",
+					},
+				},
+			},
+		},
+		{
+			name: "MSFT internal tenant and feature flag not registered",
+			sub: api.SubscriptionProperties{
+				TenantID: tenantIDMSFT,
+				RegisteredFeatures: []api.RegisteredFeatureProfile{
+					{
+						Name:  "RandomFeature",
+						State: "Registered",
+					},
+				},
+			},
+		},
+		{
+			name: "AME internal tenant and feature flag registered",
+			sub: api.SubscriptionProperties{
+				TenantID: tenantIDAME,
+				RegisteredFeatures: []api.RegisteredFeatureProfile{
+					{
+						Name:  api.FeatureFlagSaveAROTestConfig,
+						State: "Registered",
+					},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "MSFT internal tenant and feature flag registered",
+			sub: api.SubscriptionProperties{
+				TenantID: tenantIDMSFT,
+				RegisteredFeatures: []api.RegisteredFeatureProfile{
+					{
+						Name:  api.FeatureFlagSaveAROTestConfig,
+						State: "Registered",
+					},
+				},
+			},
+			want: true,
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			got := isSubscriptionRegisteredForE2E(&tt.sub)
+			if got != tt.want {
+				t.Error(got)
+			}
+		})
+	}
+}
+
 func TestDelete(t *testing.T) {
 	ctx := context.Background()
 
@@ -125,6 +220,7 @@ func TestDelete(t *testing.T) {
 			m := &manager{
 				log:       log,
 				billingDB: billingDatabase,
+				subDB:     subscriptionsDatabase,
 			}
 
 			err = m.Delete(ctx, &api.OpenShiftClusterDocument{ID: docID})
@@ -183,6 +279,12 @@ func TestEnsure(t *testing.T) {
 					ID: subID,
 					Subscription: &api.Subscription{
 						Properties: &api.SubscriptionProperties{
+							RegisteredFeatures: []api.RegisteredFeatureProfile{
+								{
+									Name:  api.FeatureFlagSaveAROTestConfig,
+									State: "NotRegistered",
+								},
+							},
 							TenantID: tenantID,
 						},
 					},
@@ -219,6 +321,12 @@ func TestEnsure(t *testing.T) {
 					ID: subID,
 					Subscription: &api.Subscription{
 						Properties: &api.SubscriptionProperties{
+							RegisteredFeatures: []api.RegisteredFeatureProfile{
+								{
+									Name:  api.FeatureFlagSaveAROTestConfig,
+									State: "NotRegistered",
+								},
+							},
 							TenantID: tenantID,
 						},
 					},
@@ -245,6 +353,12 @@ func TestEnsure(t *testing.T) {
 					ID: subID,
 					Subscription: &api.Subscription{
 						Properties: &api.SubscriptionProperties{
+							RegisteredFeatures: []api.RegisteredFeatureProfile{
+								{
+									Name:  api.FeatureFlagSaveAROTestConfig,
+									State: "NotRegistered",
+								},
+							},
 							TenantID: tenantID,
 						},
 					},
@@ -294,6 +408,7 @@ func TestEnsure(t *testing.T) {
 			m := &manager{
 				log:       log,
 				billingDB: billingDatabase,
+				subDB:     subscriptionsDatabase,
 			}
 
 			doc, err := openShiftClusterDatabase.Get(ctx, strings.ToLower(testdatabase.GetResourcePath(subID, "resourceName")))
diff --git a/pkg/util/clienthelper/clienthelper_test.go b/pkg/util/clienthelper/clienthelper_test.go
index ad695e5b2..1a7d8d4d4 100644
--- a/pkg/util/clienthelper/clienthelper_test.go
+++ b/pkg/util/clienthelper/clienthelper_test.go
@@ -1360,7 +1360,7 @@ func TestMergeApply(t *testing.T) {
 			builder := fake.NewClientBuilder().WithRuntimeObjects(tt.old)
 
 			clientFake := testclienthelper.NewHookingClient(builder.Build()).
-				WithPostUpdateHook(func(obj client.Object) error {
+				WithUpdateHook(func(obj client.Object) error {
 					beenChanged = true
 					return nil
 				})
diff --git a/pkg/util/clusterdata/service_principal_test.go b/pkg/util/clusterdata/service_principal_test.go
index 9a0b95f3e..70b940596 100644
--- a/pkg/util/clusterdata/service_principal_test.go
+++ b/pkg/util/clusterdata/service_principal_test.go
@@ -44,7 +44,7 @@ func TestClusterServidePrincipalEnricherTask(t *testing.T) {
 			}),
 			wantOc: &api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "new-client-id",
 						ClientSecret: api.SecureString("new-client-secret"),
 					},
@@ -56,7 +56,7 @@ func TestClusterServidePrincipalEnricherTask(t *testing.T) {
 			client: fake.NewSimpleClientset(),
 			wantOc: &api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "old-client-id",
 						ClientSecret: "old-client-secret",
 					},
@@ -68,7 +68,7 @@ func TestClusterServidePrincipalEnricherTask(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			oc := &api.OpenShiftCluster{
 				Properties: api.OpenShiftClusterProperties{
-					ServicePrincipalProfile: &api.ServicePrincipalProfile{
+					ServicePrincipalProfile: api.ServicePrincipalProfile{
 						ClientID:     "old-client-id",
 						ClientSecret: api.SecureString("old-client-secret"),
 					},
diff --git a/test/util/clienthelper/hookclient.go b/test/util/clienthelper/hookclient.go
index 7ce15dc28..767e6c90d 100644
--- a/test/util/clienthelper/hookclient.go
+++ b/test/util/clienthelper/hookclient.go
@@ -55,25 +55,25 @@ func NewHookingClient(c client.WithWatch) *HookingClient {
 	}
 }
 
-func (c *HookingClient) WithPostGetHook(f getFunc) *HookingClient {
+func (c *HookingClient) WithGetHook(f getFunc) *HookingClient {
 	c.postGetHook = append(c.postGetHook, f)
 	return c
 }
 
-func (c *HookingClient) WithPostDeleteHook(f hookFunc) *HookingClient {
+func (c *HookingClient) WithDeleteHook(f hookFunc) *HookingClient {
 	c.postDeleteHook = append(c.postDeleteHook, f)
 	return c
 }
 
-func (c *HookingClient) WithPostCreateHook(f hookFunc) *HookingClient {
+func (c *HookingClient) WithCreateHook(f hookFunc) *HookingClient {
 	c.postCreateHook = append(c.postCreateHook, f)
 	return c
 }
-func (c *HookingClient) WithPostUpdateHook(f hookFunc) *HookingClient {
+func (c *HookingClient) WithUpdateHook(f hookFunc) *HookingClient {
 	c.postUpdateHook = append(c.postUpdateHook, f)
 	return c
 }
-func (c *HookingClient) WithPostPatchHook(f hookFunc) *HookingClient {
+func (c *HookingClient) WithPatchHook(f hookFunc) *HookingClient {
 	c.postPatchHook = append(c.postPatchHook, f)
 	return c
 }