diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index f79b05cae..d55e55328 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.10 + image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5 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.10 + image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5 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.10 + image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5 steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.pipelines/ci.yml b/.pipelines/ci.yml index 3564656b4..ecdda2eba 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.10 + image: registry.access.redhat.com/ubi8/go-toolset:1.20.12-5 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 b95d8ba98..6eed198f4 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.10 # 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.12-5 # 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 62df20d5c..450c6cc99 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.10 # 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.12-5 # Docker image which is used to build the project https://aka.ms/obpipelines/containers Debian_Frontend: noninteractive resources: diff --git a/Dockerfile.aro-e2e b/Dockerfile.aro-e2e index 23b575fe1..da17fb2db 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.10 AS builder +FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder USER root ENV GOPATH=/root/go diff --git a/Dockerfile.aro-multistage b/Dockerfile.aro-multistage index 148f32ad4..3edac6cdb 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.10 AS builder +FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder USER root ENV GOPATH=/root/go diff --git a/Dockerfile.gatekeeper b/Dockerfile.gatekeeper index a7f3fae3c..8b781e8e3 100644 --- a/Dockerfile.gatekeeper +++ b/Dockerfile.gatekeeper @@ -1,5 +1,5 @@ ARG REGISTRY -FROM ${REGISTRY}/ubi8/go-toolset:1.20.10-1 AS builder +FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 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 df4355866..87643f947 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.10 AS builder +FROM ${REGISTRY}/ubi8/go-toolset:1.20.12-5 AS builder USER root ENV GOPATH=/root/go RUN mkdir -p /app diff --git a/Makefile b/Makefile index e04d4aaa0..3dbbbbff4 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ NO_CACHE ?= true export GOFLAGS=$(GO_FLAGS) # fluentbit version must also be updated in RP code, see pkg/util/version/const.go -MARINER_VERSION = 20230321 +MARINER_VERSION = 20240301 FLUENTBIT_VERSION = 1.9.10 FLUENTBIT_IMAGE ?= ${RP_IMAGE_ACR}.azurecr.io/fluentbit:$(FLUENTBIT_VERSION)-cm$(MARINER_VERSION) AUTOREST_VERSION = 3.6.3 @@ -114,7 +114,8 @@ generate-kiota: go run ./hack/licenses -dirs ./pkg/util/graph/graphsdk init-contrib: - cp -R hack/git/hooks/* .git/hooks/ + install -v 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) . diff --git a/docs/deploy-development-rp.md b/docs/deploy-development-rp.md index aa0f4ed0f..b3c48b1e3 100644 --- a/docs/deploy-development-rp.md +++ b/docs/deploy-development-rp.md @@ -9,24 +9,10 @@ 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 < 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 @@ -41,7 +27,10 @@ ``` Note: you will be able to update your development `az aro` extension in the - future by simply running `git pull`. + 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). + ## Prepare your environment diff --git a/docs/deploy-full-rp-service-in-dev.md b/docs/deploy-full-rp-service-in-dev.md index 1503996bc..0d06d4cf5 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 ipecho.net/plain)/32" \ + --source-address-prefixes "$(curl --silent -4 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 ipecho.net/plain)/32" \ + --source-address-prefixes "$(curl --silent -4 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 ipecho.net/plain)/32" \ + --source-address-prefixes "$(curl --silent -4 ipecho.net/plain)/32" \ --protocol Tcp \ --destination-port-ranges 443 ``` diff --git a/docs/feature-flags.md b/docs/feature-flags.md index 30fff4084..467ebf113 100644 --- a/docs/feature-flags.md +++ b/docs/feature-flags.md @@ -20,10 +20,6 @@ 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 cba63ee6d..f252b2d09 100644 --- a/docs/prepare-your-dev-environment.md +++ b/docs/prepare-your-dev-environment.md @@ -139,8 +139,10 @@ 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. Add standard git hooks +1. Configure local git ```bash make init-contrib + git config --global github.user <> ``` + diff --git a/env-int.example b/env-int.example index 08e0d574c..ea40b6053 100644 --- a/env-int.example +++ b/env-int.example @@ -5,5 +5,6 @@ export RESOURCEGROUP=$USER-aro-$LOCATION export DATABASE_ACCOUNT_NAME=$USER-aro-$LOCATION export DATABASE_NAME=ARO export KEYVAULT_PREFIX=$USER-aro-$LOCATION +export KEYVAULT_PREFIX=${KEYVAULT_PREFIX::20} export ARO_IMAGE=${USER}aro.azurecr.io/aro:$(git rev-parse --short=7 HEAD)$([[ $(git status --porcelain) = "" ]] || echo -dirty) -export FLUENTBIT_IMAGE=${USER}aro.azurecr.io/fluentbit:latest \ No newline at end of file +export FLUENTBIT_IMAGE=${USER}aro.azurecr.io/fluentbit:latest diff --git a/env.example b/env.example index 7cea448a1..a265550d1 100644 --- a/env.example +++ b/env.example @@ -1,5 +1,6 @@ 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/go.mod b/go.mod index c3e384f47..b3ad73aa3 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.20 require ( github.com/Azure/azure-sdk-for-go v63.1.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.5.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1 github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/adal v0.9.23 diff --git a/go.sum b/go.sum index 1da030279..b84beb0f9 100644 --- a/go.sum +++ b/go.sum @@ -5,13 +5,16 @@ github.com/Azure/azure-sdk-for-go v63.1.0+incompatible h1:yNC7qlSUWVF8p0TzxdmWW1 github.com/Azure/azure-sdk-for-go v63.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.5.0 h1:FTNvxTFH/08JBmhcbL5lmLaGYVXokZM6Ni92Mqr+gSg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.5.0/go.mod h1:T0ryqIz5h5qg4HOBni+VeRn24alSqOx1Se1IAwUByOk= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.4.0 h1:HlZMUZW8S4P9oob1nCHxCCKrytxyLc+24nUJGssoEto= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.4.0/go.mod h1:StGsLbuJh06Bd8IBfnAlIFV3fLb+gkczONWf15hpX2E= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1 h1:bWh0Z2rOEDfB/ywv/l0iHN1JgyazE6kW/aIA89+CEK0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1/go.mod h1:Bzf34hhAE9NSxailk8xVeLEZbUjOXcC+GnU1mMKdhLw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= diff --git a/pkg/api/admin/platformworkloadidentityroleset.go b/pkg/api/admin/platformworkloadidentityroleset.go new file mode 100644 index 000000000..9ed4680bc --- /dev/null +++ b/pkg/api/admin/platformworkloadidentityroleset.go @@ -0,0 +1,46 @@ +package admin + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +// PlatformWorkloadIdentityRoleSetList represents a List of role sets. +type PlatformWorkloadIdentityRoleSetList struct { + // The list of role sets. + PlatformWorkloadIdentityRoleSets []*PlatformWorkloadIdentityRoleSet `json:"value"` +} + +// PlatformWorkloadIdentityRoleSet represents a mapping from the names of OCP operators to the built-in roles that should be assigned to those operator's corresponding managed identities for a particular OCP version. +type PlatformWorkloadIdentityRoleSet struct { + // The ID for the resource. + ID string `json:"id,omitempty" mutable:"case"` + + // Name of the resource. + Name string `json:"name,omitempty" mutable:"case"` + + // The properties for the PlatformWorkloadIdentityRoleSet resource. + Properties PlatformWorkloadIdentityRoleSetProperties `json:"properties,omitempty"` +} + +// PlatformWorkloadIdentityRoleSetProperties represents the properties of a PlatformWorkloadIdentityRoleSet resource. +type PlatformWorkloadIdentityRoleSetProperties struct { + // OpenShiftVersion represents the version associated with this set of roles. + OpenShiftVersion string `json:"openShiftVersion,omitempty"` + + // PlatformWorkloadIdentityRoles represents the set of roles associated with this version. + PlatformWorkloadIdentityRoles []PlatformWorkloadIdentityRole `json:"platformWorkloadIdentityRoles,omitempty" mutable:"true"` +} + +// PlatformWorkloadIdentityRole represents a mapping from a particular OCP operator to the built-in role that should be assigned to that operator's corresponding managed identity. +type PlatformWorkloadIdentityRole struct { + // OperatorName represents the name of the operator that this role is for. + OperatorName string `json:"operatorName,omitempty" mutable:"true"` + + // RoleDefinitionName represents the name of the role. + RoleDefinitionName string `json:"roleDefinitionName,omitempty" mutable:"true"` + + // RoleDefinitionID represents the resource ID of the role definition. + RoleDefinitionID string `json:"roleDefinitionId,omitempty" mutable:"true"` + + // ServiceAccounts represents the set of service accounts associated with the given operator, since each service account needs its own federated credential. + ServiceAccounts []string `json:"serviceAccounts,omitempty" mutable:"true"` +} diff --git a/pkg/api/admin/platformworkloadidentityroleset_convert.go b/pkg/api/admin/platformworkloadidentityroleset_convert.go new file mode 100644 index 000000000..b1caa21cb --- /dev/null +++ b/pkg/api/admin/platformworkloadidentityroleset_convert.go @@ -0,0 +1,67 @@ +package admin + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +/* +TODO: Uncomment once API endpoints have been implemented and this code is being used. + +type platformWorkloadIdentityRoleSetConverter struct{} + +// platformWorkloadIdentityRoleSetConverter.ToExternal returns a new external representation +// of the internal object, reading from the subset of the internal object's +// fields that appear in the external representation. ToExternal does not +// modify its argument; there is no pointer aliasing between the passed and +// returned objects. +func (c platformWorkloadIdentityRoleSetConverter) ToExternal(s *api.PlatformWorkloadIdentityRoleSet) interface{} { + out := &PlatformWorkloadIdentityRoleSet{ + Properties: PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: s.Properties.OpenShiftVersion, + PlatformWorkloadIdentityRoles: make([]PlatformWorkloadIdentityRole, 0, len(s.Properties.PlatformWorkloadIdentityRoles)), + }, + } + + for i, r := range s.Properties.PlatformWorkloadIdentityRoles { + out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID + out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = make([]string, 0, len(r.ServiceAccounts)) + out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = append(out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts, r.ServiceAccounts...) + } + + return out +} + +// ToExternalList returns a slice of external representations of the internal +// objects +func (c platformWorkloadIdentityRoleSetConverter) ToExternalList(sets []*api.PlatformWorkloadIdentityRoleSet) interface{} { + l := &PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: make([]*PlatformWorkloadIdentityRoleSet, 0, len(sets)), + } + + for _, set := range sets { + l.PlatformWorkloadIdentityRoleSets = append(l.PlatformWorkloadIdentityRoleSets, c.ToExternal(set).(*PlatformWorkloadIdentityRoleSet)) + } + + return l +} + +// ToInternal overwrites in place a pre-existing internal object, setting (only) +// all mapped fields from the external representation. ToInternal modifies its +// argument; there is no pointer aliasing between the passed and returned +// objects +func (c platformWorkloadIdentityRoleSetConverter) ToInternal(_new interface{}, out *api.PlatformWorkloadIdentityRoleSet) { + new := _new.(*PlatformWorkloadIdentityRoleSet) + + out.Properties.OpenShiftVersion = new.Properties.OpenShiftVersion + out.Properties.PlatformWorkloadIdentityRoles = make([]api.PlatformWorkloadIdentityRole, 0, len(new.Properties.PlatformWorkloadIdentityRoles)) + + for i, r := range new.Properties.PlatformWorkloadIdentityRoles { + out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID + out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = make([]string, 0, len(r.ServiceAccounts)) + out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = append(out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts, r.ServiceAccounts...) + } +} +*/ diff --git a/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go b/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go new file mode 100644 index 000000000..e70bffc05 --- /dev/null +++ b/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go @@ -0,0 +1,71 @@ +package admin + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +/* +TODO: Uncomment once API endpoints have been implemented and this code is being used. + +type platformWorkloadIdentityRoleSetStaticValidator struct{} + +func (sv platformWorkloadIdentityRoleSetStaticValidator) Static(_new interface{}, _current *api.PlatformWorkloadIdentityRoleSet) error { + new := _new.(*PlatformWorkloadIdentityRoleSet) + + var current *PlatformWorkloadIdentityRoleSet + if _current != nil { + current = (&platformWorkloadIdentityRoleSetConverter{}).ToExternal(_current).(*PlatformWorkloadIdentityRoleSet) + } + + err := sv.validate(new, current == nil) + if err != nil { + return err + } + + if current == nil { + return nil + } + + return sv.validateDelta(new, current) +} + +func (sv platformWorkloadIdentityRoleSetStaticValidator) validate(new *PlatformWorkloadIdentityRoleSet, isCreate bool) error { + if new.Properties.OpenShiftVersion == "" { + return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.openShiftVersion", "Must be provided") + } + + if new.Properties.PlatformWorkloadIdentityRoles == nil || len(new.Properties.PlatformWorkloadIdentityRoles) == 0 { + return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.platformWorkloadIdentityRoles", "Must be provided and must be non-empty") + } + + errs := []error{} + + for i, r := range new.Properties.PlatformWorkloadIdentityRoles { + if r.OperatorName == "" { + errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].operatorName", i), "Must be provided")) + } + + if r.RoleDefinitionName == "" { + errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].roleDefinitionName", i), "Must be provided")) + } + + if r.RoleDefinitionID == "" { + errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].roleDefinitionId", i), "Must be provided")) + } + + if r.ServiceAccounts == nil || len(r.ServiceAccounts) == 0 { + errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].serviceAccounts", i), "Must be provided and must be non-empty")) + } + } + + return errors.Join(errs...) +} + +func (sv platformWorkloadIdentityRoleSetStaticValidator) validateDelta(new, current *PlatformWorkloadIdentityRoleSet) error { + err := immutable.Validate("", new, current) + if err != nil { + err := err.(*immutable.ValidationError) + return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodePropertyChangeNotAllowed, err.Target, err.Message) + } + return nil +} +*/ diff --git a/pkg/api/featureflags.go b/pkg/api/featureflags.go index 31ef71f1f..2f3ad77f1 100644 --- a/pkg/api/featureflags.go +++ b/pkg/api/featureflags.go @@ -4,11 +4,6 @@ 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/platformworkloadidentityroleset.go b/pkg/api/platformworkloadidentityroleset.go new file mode 100644 index 000000000..372eab1c8 --- /dev/null +++ b/pkg/api/platformworkloadidentityroleset.go @@ -0,0 +1,30 @@ +package api + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +// PlatformWorkloadIdentityRoleSet represents a mapping from the names of OCP operators to the built-in roles that should be assigned to those operator's corresponding managed identities for a particular OCP version. +type PlatformWorkloadIdentityRoleSet struct { + MissingFields + + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Deleting bool `json:"deleting,omitempty"` // https://docs.microsoft.com/en-us/azure/cosmos-db/change-feed-design-patterns#deletes + + Properties PlatformWorkloadIdentityRoleSetProperties `json:"properties,omitempty"` +} + +// PlatformWorkloadIdentityRoleSetProperties represents the properties of a PlatformWorkloadIdentityRoleSet resource. +type PlatformWorkloadIdentityRoleSetProperties struct { + OpenShiftVersion string `json:"openShiftVersion,omitempty"` + PlatformWorkloadIdentityRoles []PlatformWorkloadIdentityRole `json:"platformWorkloadIdentityRoles,omitempty"` +} + +// PlatformWorkloadIdentityRole represents a mapping from a particular OCP operator to the built-in role that should be assigned to that operator's corresponding managed identity. +type PlatformWorkloadIdentityRole struct { + OperatorName string `json:"operatorName,omitempty"` + RoleDefinitionName string `json:"roleDefinitionName,omitempty"` + RoleDefinitionID string `json:"roleDefinitionId,omitempty"` + ServiceAccounts []string `json:"serviceAccounts,omitempty"` +} diff --git a/pkg/api/platformworkloadidentityrolesetdocument.go b/pkg/api/platformworkloadidentityrolesetdocument.go new file mode 100644 index 000000000..f9eee2d8b --- /dev/null +++ b/pkg/api/platformworkloadidentityrolesetdocument.go @@ -0,0 +1,38 @@ +package api + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +// PlatformWorkloadIdentityRoleSetDocuments represents a set of PlatformWorkloadIdentityRoleSetDocuments. +// pkg/database/cosmosdb requires its definition. +type PlatformWorkloadIdentityRoleSetDocuments struct { + Count int `json:"_count,omitempty"` + ResourceID string `json:"_rid,omitempty"` + PlatformWorkloadIdentityRoleSetDocuments []*PlatformWorkloadIdentityRoleSetDocument `json:"Documents,omitempty"` +} + +func (c *PlatformWorkloadIdentityRoleSetDocuments) String() string { + return encodeJSON(c) +} + +// PlatformWorkloadIdentityRoleSetDocument represents a document specifying a mapping from the names of OCP operators to the built-in roles that should be assigned to those operator's corresponding managed identities for a particular OCP version. +// pkg/database/cosmosdb requires its definition. +type PlatformWorkloadIdentityRoleSetDocument struct { + MissingFields + + ID string `json:"id,omitempty"` + ResourceID string `json:"_rid,omitempty"` + Timestamp int `json:"_ts,omitempty"` + Self string `json:"_self,omitempty"` + ETag string `json:"_etag,omitempty" deep:"-"` + Attachments string `json:"_attachments,omitempty"` + TTL int `json:"ttl,omitempty"` + LSN int `json:"_lsn,omitempty"` + Metadata map[string]interface{} `json:"_metadata,omitempty"` + + PlatformWorkloadIdentityRoleSet *PlatformWorkloadIdentityRoleSet `json:"platformWorkloadIdentityRoleSet,omitempty"` +} + +func (c *PlatformWorkloadIdentityRoleSetDocument) String() string { + return encodeJSON(c) +} diff --git a/pkg/api/platformworkloadidentityrolesetdocument_example.go b/pkg/api/platformworkloadidentityrolesetdocument_example.go new file mode 100644 index 000000000..12fd32f23 --- /dev/null +++ b/pkg/api/platformworkloadidentityrolesetdocument_example.go @@ -0,0 +1,29 @@ +package api + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +func ExamplePlatformWorkloadIdentityRoleSetDocument() *PlatformWorkloadIdentityRoleSetDocument { + return &PlatformWorkloadIdentityRoleSetDocument{ + MissingFields: MissingFields{}, + ID: "00000000-0000-0000-0000-000000000000", + PlatformWorkloadIdentityRoleSet: &PlatformWorkloadIdentityRoleSet{ + ID: "00000000-0000-0000-0000-000000000000", + Name: "4.14", + Type: "Microsoft.RedHatOpenShift/PlatformWorkloadIdentityRoleSet", + Properties: PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []PlatformWorkloadIdentityRole{ + { + OperatorName: "ServiceOperator", + RoleDefinitionName: "AzureRedHatOpenShiftServiceOperator", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/00000000-0000-0000-0000-000000000000", + ServiceAccounts: []string{ + "aro-operator-master", + }, + }, + }, + }, + }, + } +} diff --git a/pkg/api/pretty.go b/pkg/api/pretty.go index de3b07a8c..7d0bbcc06 100644 --- a/pkg/api/pretty.go +++ b/pkg/api/pretty.go @@ -15,13 +15,7 @@ import ( var secretPreservingJSONHandle *codec.JsonHandle func init() { - secretPreservingJSONHandle = &codec.JsonHandle{ - BasicHandle: codec.BasicHandle{ - DecodeOptions: codec.DecodeOptions{ - ErrorIfNoField: true, - }, - }, - } + secretPreservingJSONHandle = &codec.JsonHandle{} err := secretPreservingJSONHandle.SetInterfaceExt(reflect.TypeOf(SecureBytes{}), 1, secureHidingExt{}) if err != nil { diff --git a/pkg/api/v20231122/openshiftcluster_convert.go b/pkg/api/v20231122/openshiftcluster_convert.go index ab633f4d5..f4f107665 100644 --- a/pkg/api/v20231122/openshiftcluster_convert.go +++ b/pkg/api/v20231122/openshiftcluster_convert.go @@ -277,4 +277,11 @@ func (c openShiftClusterConverter) ExternalNoReadOnly(_oc interface{}) { if oc.Properties.NetworkProfile.LoadBalancerProfile != nil { oc.Properties.NetworkProfile.LoadBalancerProfile.EffectiveOutboundIPs = nil } + oc.SystemData = nil + oc.Properties.ConsoleProfile.URL = "" + oc.Properties.APIServerProfile.URL = "" + oc.Properties.APIServerProfile.IP = "" + for i := range oc.Properties.IngressProfiles { + oc.Properties.IngressProfiles[i].IP = "" + } } diff --git a/pkg/api/v20240812preview/openshiftcluster_convert.go b/pkg/api/v20240812preview/openshiftcluster_convert.go index 45c82a7bf..295c77c07 100644 --- a/pkg/api/v20240812preview/openshiftcluster_convert.go +++ b/pkg/api/v20240812preview/openshiftcluster_convert.go @@ -326,4 +326,15 @@ func (c openShiftClusterConverter) ExternalNoReadOnly(_oc interface{}) { if oc.Properties.NetworkProfile.LoadBalancerProfile != nil { oc.Properties.NetworkProfile.LoadBalancerProfile.EffectiveOutboundIPs = nil } + oc.SystemData = nil + oc.Properties.ConsoleProfile.URL = "" + oc.Properties.APIServerProfile.URL = "" + oc.Properties.APIServerProfile.IP = "" + for i := range oc.Properties.IngressProfiles { + oc.Properties.IngressProfiles[i].IP = "" + } + for i := range oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities { + oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities[i].ClientID = "" + oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities[i].ObjectID = "" + } } diff --git a/pkg/api/v20240812preview/platformworkloadidentityroleset.go b/pkg/api/v20240812preview/platformworkloadidentityroleset.go new file mode 100644 index 000000000..b944b9675 --- /dev/null +++ b/pkg/api/v20240812preview/platformworkloadidentityroleset.go @@ -0,0 +1,51 @@ +package v20240812preview + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +// PlatformWorkloadIdentityRoleSetList represents a List of role sets. +type PlatformWorkloadIdentityRoleSetList struct { + // The list of role sets. + PlatformWorkloadIdentityRoleSets []*PlatformWorkloadIdentityRoleSet `json:"value"` + + // Next Link to next operation. + NextLink string `json:"nextLink,omitempty"` +} + +// PlatformWorkloadIdentityRoleSet represents a mapping from the names of OCP operators to the built-in roles that should be assigned to those operator's corresponding managed identities for a particular OCP version. +type PlatformWorkloadIdentityRoleSet struct { + proxyResource bool + + // The ID for the resource. + ID string `json:"id,omitempty" mutable:"case"` + + // Name of the resource. + Name string `json:"name,omitempty" mutable:"case"` + + // The resource type. + Type string `json:"type,omitempty" mutable:"case"` + + // The properties for the PlatformWorkloadIdentityRoleSet resource. + Properties PlatformWorkloadIdentityRoleSetProperties `json:"properties,omitempty"` +} + +// PlatformWorkloadIdentityRoleSetProperties represents the properties of a PlatformWorkloadIdentityRoleSet resource. +type PlatformWorkloadIdentityRoleSetProperties struct { + // OpenShiftVersion represents the version associated with this set of roles. + OpenShiftVersion string `json:"openShiftVersion,omitempty"` + + // PlatformWorkloadIdentityRoles represents the set of roles associated with this version. + PlatformWorkloadIdentityRoles []PlatformWorkloadIdentityRole `json:"platformWorkloadIdentityRoles,omitempty"` +} + +// PlatformWorkloadIdentityRole represents a mapping from a particular OCP operator to the built-in role that should be assigned to that operator's corresponding managed identity. +type PlatformWorkloadIdentityRole struct { + // OperatorName represents the name of the operator that this role is for. + OperatorName string `json:"operatorName,omitempty"` + + // RoleDefinitionName represents the name of the role. + RoleDefinitionName string `json:"roleDefinitionName,omitempty"` + + // RoleDefinitionID represents the resource ID of the role definition. + RoleDefinitionID string `json:"roleDefinitionId,omitempty"` +} diff --git a/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go b/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go new file mode 100644 index 000000000..a651229c1 --- /dev/null +++ b/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go @@ -0,0 +1,65 @@ +package v20240812preview + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "github.com/Azure/ARO-RP/pkg/api" +) + +type platformWorkloadIdentityRoleSetConverter struct{} + +// platformWorkloadIdentityRoleSetConverter.ToExternal returns a new external representation +// of the internal object, reading from the subset of the internal object's +// fields that appear in the external representation. ToExternal does not +// modify its argument; there is no pointer aliasing between the passed and +// returned objects. +func (c platformWorkloadIdentityRoleSetConverter) ToExternal(s *api.PlatformWorkloadIdentityRoleSet) interface{} { + out := &PlatformWorkloadIdentityRoleSet{ + ID: s.ID, + proxyResource: true, + Properties: PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: s.Properties.OpenShiftVersion, + PlatformWorkloadIdentityRoles: make([]PlatformWorkloadIdentityRole, 0, len(s.Properties.PlatformWorkloadIdentityRoles)), + }, + } + + for i, r := range s.Properties.PlatformWorkloadIdentityRoles { + out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID + } + + return out +} + +// ToExternalList returns a slice of external representations of the internal +// objects +func (c platformWorkloadIdentityRoleSetConverter) ToExternalList(sets []*api.PlatformWorkloadIdentityRoleSet) interface{} { + l := &PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: make([]*PlatformWorkloadIdentityRoleSet, 0, len(sets)), + } + + for _, set := range sets { + l.PlatformWorkloadIdentityRoleSets = append(l.PlatformWorkloadIdentityRoleSets, c.ToExternal(set).(*PlatformWorkloadIdentityRoleSet)) + } + + return l +} + +// ToInternal overwrites in place a pre-existing internal object, setting (only) +// all mapped fields from the external representation. ToInternal modifies its +// argument; there is no pointer aliasing between the passed and returned +// objects +func (c platformWorkloadIdentityRoleSetConverter) ToInternal(_new interface{}, out *api.PlatformWorkloadIdentityRoleSet) { + new := _new.(*PlatformWorkloadIdentityRoleSet) + + out.Properties.OpenShiftVersion = new.Properties.OpenShiftVersion + out.Properties.PlatformWorkloadIdentityRoles = make([]api.PlatformWorkloadIdentityRole, 0, len(new.Properties.PlatformWorkloadIdentityRoles)) + + for i, r := range new.Properties.PlatformWorkloadIdentityRoles { + out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName + out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID + } +} diff --git a/pkg/api/v20240812preview/platformworkloadidentityroleset_example.go b/pkg/api/v20240812preview/platformworkloadidentityroleset_example.go new file mode 100644 index 000000000..6cf6a1d09 --- /dev/null +++ b/pkg/api/v20240812preview/platformworkloadidentityroleset_example.go @@ -0,0 +1,24 @@ +package v20240812preview + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import "github.com/Azure/ARO-RP/pkg/api" + +func examplePlatformWorkloadIdentityRoleSet() *PlatformWorkloadIdentityRoleSet { + doc := api.ExamplePlatformWorkloadIdentityRoleSetDocument() + ext := (&platformWorkloadIdentityRoleSetConverter{}).ToExternal(doc.PlatformWorkloadIdentityRoleSet) + return ext.(*PlatformWorkloadIdentityRoleSet) +} + +func ExamplePlatformWorkloadIdentityRoleSetResponse() interface{} { + return examplePlatformWorkloadIdentityRoleSet() +} + +func ExamplePlatformWorkloadIdentityRoleSetListResponse() interface{} { + return &PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: []*PlatformWorkloadIdentityRoleSet{ + ExamplePlatformWorkloadIdentityRoleSetResponse().(*PlatformWorkloadIdentityRoleSet), + }, + } +} diff --git a/pkg/containerinstall/install_test.go b/pkg/containerinstall/install_test.go index 620a7e62f..12a075ab5 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.10" +const TEST_PULLSPEC = "registry.access.redhat.com/ubi8/go-toolset:1.20.12-5" var _ = Describe("Podman", Ordered, func() { var err error diff --git a/pkg/database/database.go b/pkg/database/database.go index 9c8c230c0..4e6ad73d7 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -78,13 +78,7 @@ func getDatabaseKey(keys sdkcosmos.DatabaseAccountsClientListKeysResponse, log * } func NewJSONHandle(aead encryption.AEAD) (*codec.JsonHandle, error) { - h := &codec.JsonHandle{ - BasicHandle: codec.BasicHandle{ - DecodeOptions: codec.DecodeOptions{ - ErrorIfNoField: true, - }, - }, - } + h := &codec.JsonHandle{} if aead == nil { return h, nil diff --git a/pkg/deploy/assets/databases-development.json b/pkg/deploy/assets/databases-development.json index 0216939f1..d0c7aabfa 100644 --- a/pkg/deploy/assets/databases-development.json +++ b/pkg/deploy/assets/databases-development.json @@ -11,160 +11,167 @@ }, "resources": [ { + "apiVersion": "2023-04-15", + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'))]", "properties": { - "resource": { - "id": "[parameters('databaseName')]" - }, "options": { "autoscaleSettings": { "maxThroughput": 1000 } + }, + "resource": { + "id": "[parameters('databaseName')]" } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'))]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15" + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/AsyncOperations')]", "properties": { + "options": {}, "resource": { + "defaultTtl": 604800, "id": "AsyncOperations", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": 604800 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/AsyncOperations')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/OpenShiftVersions')]", "properties": { + "options": {}, "resource": { + "defaultTtl": -1, "id": "OpenShiftVersions", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/OpenShiftVersions')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/ClusterManagerConfigurations')]", "properties": { + "options": {}, "resource": { "id": "ClusterManagerConfigurations", "partitionKey": { + "kind": "Hash", "paths": [ "/partitionKey" - ], - "kind": "Hash" + ] } - }, - "options": {} + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/ClusterManagerConfigurations')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Billing')]", "properties": { + "options": {}, "resource": { "id": "Billing", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" + ] } - }, - "options": {} + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Billing')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Gateway')]", "properties": { + "options": {}, "resource": { + "defaultTtl": -1, "id": "Gateway", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Gateway')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Monitors')]", "properties": { + "options": {}, "resource": { + "defaultTtl": -1, "id": "Monitors", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Monitors')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/OpenShiftClusters')]", "properties": { + "options": {}, "resource": { "id": "OpenShiftClusters", "partitionKey": { + "kind": "Hash", "paths": [ "/partitionKey" - ], - "kind": "Hash" + ] }, "uniqueKeyPolicy": { "uniqueKeys": [ @@ -185,59 +192,52 @@ } ] } - }, - "options": {} + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/OpenShiftClusters')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Portal')]", "properties": { + "options": {}, "resource": { + "defaultTtl": -1, "id": "Portal", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Portal')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Subscriptions')]", "properties": { + "options": {}, "resource": { "id": "Subscriptions", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" + ] } - }, - "options": {} + } }, - "name": "[concat(parameters('databaseAccountName'), '/', parameters('databaseName'), '/Subscriptions')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('databaseName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" } ] } diff --git a/pkg/deploy/assets/env-development.json b/pkg/deploy/assets/env-development.json index 10057c045..4328b7592 100644 --- a/pkg/deploy/assets/env-development.json +++ b/pkg/deploy/assets/env-development.json @@ -224,7 +224,7 @@ }, "properties": { "upgradePolicy": { - "mode": "Manual" + "mode": "Rolling" }, "virtualMachineProfile": { "osProfile": { @@ -296,7 +296,7 @@ "autoUpgradeMinorVersion": true, "settings": {}, "protectedSettings": { - "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'PROXYIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImage')),''')\n','PROXYIMAGEAUTH=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImageAuth')),''')\n','PROXYCERT=''',parameters('proxyCert'),'''\n','PROXYCLIENTCERT=''',parameters('proxyClientCert'),'''\n','PROXYKEY=''',parameters('proxyKey'),'''\n','\n',base64ToString('I0FkZGluZyByZXRyeSBsb2dpYyB0byB5dW0gY29tbWFuZHMgaW4gb3JkZXIgdG8gYXZvaWQgc3RhbGxpbmcgb3V0IG9uIHJlc291cmNlIGxvY2tzCmVjaG8gInJ1bm5pbmcgUkhVSSBmaXgiCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KICB5dW0gdXBkYXRlIC15IC0tZGlzYWJsZXJlcG89JyonIC0tZW5hYmxlcmVwbz0ncmh1aS1taWNyb3NvZnQtYXp1cmUqJyAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJydW5uaW5nIHl1bSB1cGRhdGUiCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KICB5dW0gLXkgLXggV0FMaW51eEFnZW50IC14IFdBTGludXhBZ2VudC11ZGV2IHVwZGF0ZSAtLWFsbG93ZXJhc2luZyAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJpbnN0YWxsaW5nIHBvZG1hbi1kb2NrZXIiCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KICB5dW0gLXkgaW5zdGFsbCBwb2RtYW4tZG9ja2VyICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0My90Y3AgLS1wZXJtYW5lbnQKCm1rZGlyIC9yb290Ly5kb2NrZXIKY2F0ID4vcm9vdC8uZG9ja2VyL2NvbmZpZy5qc29uIDw8RU9GCnsKCSJhdXRocyI6IHsKCQkiJHtQUk9YWUlNQUdFJSUvKn0iOiB7CgkJCSJhdXRoIjogIiRQUk9YWUlNQUdFQVVUSCIKCQl9Cgl9Cn0KRU9GCgpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKZG9ja2VyIHB1bGwgIiRQUk9YWUlNQUdFIgoKbWtkaXIgL2V0Yy9wcm94eQpiYXNlNjQgLWQgPDw8IiRQUk9YWUNFUlQiID4vZXRjL3Byb3h5L3Byb3h5LmNydApiYXNlNjQgLWQgPDw8IiRQUk9YWUtFWSIgPi9ldGMvcHJveHkvcHJveHkua2V5CmJhc2U2NCAtZCA8PDwiJFBST1hZQ0xJRU5UQ0VSVCIgPi9ldGMvcHJveHkvcHJveHktY2xpZW50LmNydApjaG93biAtUiAxMDAwOjEwMDAgL2V0Yy9wcm94eQpjaG1vZCAwNjAwIC9ldGMvcHJveHkvcHJveHkua2V5CgpjYXQgPi9ldGMvc3lzY29uZmlnL3Byb3h5IDw8RU9GClBST1hZX0lNQUdFPSckUFJPWFlJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vcHJveHkuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL3Byb3h5CkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVuCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIC0tcm0gLS1uYW1lICVuIC1wIDQ0Mzo4NDQzIC12IC9ldGMvcHJveHk6L3NlY3JldHMgJFBST1hZX0lNQUdFCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVuClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKc3lzdGVtY3RsIGVuYWJsZSBwcm94eS5zZXJ2aWNlCgpjYXQgPi9ldGMvY3Jvbi53ZWVrbHkvcHVsbC1pbWFnZSA8PCdFT0YnCiMhL2Jpbi9iYXNoCgpkb2NrZXIgcHVsbCAkUFJPWFlJTUFHRQpzeXN0ZW1jdGwgcmVzdGFydCBwcm94eS5zZXJ2aWNlCkVPRgpjaG1vZCAreCAvZXRjL2Nyb24ud2Vla2x5L3B1bGwtaW1hZ2UKCmNhdCA+L2V0Yy9jcm9uLndlZWtseS95dW11cGRhdGUgPDwnRU9GJwojIS9iaW4vYmFzaAoKeXVtIHVwZGF0ZSAteQpFT0YKY2htb2QgK3ggL2V0Yy9jcm9uLndlZWtseS95dW11cGRhdGUKCmNhdCA+L2V0Yy9jcm9uLmRhaWx5L3Jlc3RhcnQtcHJveHkgPDwnRU9GJwojIS9iaW4vYmFzaAoKc3lzdGVtY3RsIHJlc3RhcnQgcHJveHkuc2VydmljZQpFT0YKY2htb2QgK3ggL2V0Yy9jcm9uLmRhaWx5L3Jlc3RhcnQtcHJveHkKCigKCXNsZWVwIDMwCglyZWJvb3QKKSAmCg==')))]" + "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'PROXYIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImage')),''')\n','PROXYIMAGEAUTH=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImageAuth')),''')\n','PROXYCERT=''',parameters('proxyCert'),'''\n','PROXYCLIENTCERT=''',parameters('proxyClientCert'),'''\n','PROXYKEY=''',parameters('proxyKey'),'''\n','\n',base64ToString('I0FkZGluZyByZXRyeSBsb2dpYyB0byB5dW0gY29tbWFuZHMgaW4gb3JkZXIgdG8gYXZvaWQgc3RhbGxpbmcgb3V0IG9uIHJlc291cmNlIGxvY2tzCmVjaG8gInJ1bm5pbmcgUkhVSSBmaXgiCmZvciBhdHRlbXB0IGluIHsxLi42MH07IGRvCiAgeXVtIHVwZGF0ZSAteSAtLWRpc2FibGVyZXBvPScqJyAtLWVuYWJsZXJlcG89J3JodWktbWljcm9zb2Z0LWF6dXJlKicgJiYgYnJlYWsKICBpZiBbWyAke2F0dGVtcHR9IC1sdCA2MCBdXTsgdGhlbiBzbGVlcCAzMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gInJ1bm5pbmcgeXVtIHVwZGF0ZSIKZm9yIGF0dGVtcHQgaW4gezEuLjYwfTsgZG8KICB5dW0gLXkgLXggV0FMaW51eEFnZW50IC14IFdBTGludXhBZ2VudC11ZGV2IHVwZGF0ZSAtLWFsbG93ZXJhc2luZyAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAiaW5zdGFsbGluZyBwb2RtYW4tZG9ja2VyIgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwogIHl1bSAteSBpbnN0YWxsIHBvZG1hbi1kb2NrZXIgJiYgYnJlYWsKICBpZiBbWyAke2F0dGVtcHR9IC1sdCA2MCBdXTsgdGhlbiBzbGVlcCAzMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0My90Y3AgLS1wZXJtYW5lbnQKCm1rZGlyIC9yb290Ly5kb2NrZXIKY2F0ID4vcm9vdC8uZG9ja2VyL2NvbmZpZy5qc29uIDw8RU9GCnsKCSJhdXRocyI6IHsKCQkiJHtQUk9YWUlNQUdFJSUvKn0iOiB7CgkJCSJhdXRoIjogIiRQUk9YWUlNQUdFQVVUSCIKCQl9Cgl9Cn0KRU9GCgpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKZG9ja2VyIHB1bGwgIiRQUk9YWUlNQUdFIgoKbWtkaXIgL2V0Yy9wcm94eQpiYXNlNjQgLWQgPDw8IiRQUk9YWUNFUlQiID4vZXRjL3Byb3h5L3Byb3h5LmNydApiYXNlNjQgLWQgPDw8IiRQUk9YWUtFWSIgPi9ldGMvcHJveHkvcHJveHkua2V5CmJhc2U2NCAtZCA8PDwiJFBST1hZQ0xJRU5UQ0VSVCIgPi9ldGMvcHJveHkvcHJveHktY2xpZW50LmNydApjaG93biAtUiAxMDAwOjEwMDAgL2V0Yy9wcm94eQpjaG1vZCAwNjAwIC9ldGMvcHJveHkvcHJveHkua2V5CgpjYXQgPi9ldGMvc3lzY29uZmlnL3Byb3h5IDw8RU9GClBST1hZX0lNQUdFPSckUFJPWFlJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vcHJveHkuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL3Byb3h5CkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVuCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIC0tcm0gLS1uYW1lICVuIC1wIDQ0Mzo4NDQzIC12IC9ldGMvcHJveHk6L3NlY3JldHMgJFBST1hZX0lNQUdFCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVuClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKc3lzdGVtY3RsIGVuYWJsZSBwcm94eS5zZXJ2aWNlCgpjYXQgPi9ldGMvY3Jvbi53ZWVrbHkvcHVsbC1pbWFnZSA8PCdFT0YnCiMhL2Jpbi9iYXNoCgpkb2NrZXIgcHVsbCAkUFJPWFlJTUFHRQpzeXN0ZW1jdGwgcmVzdGFydCBwcm94eS5zZXJ2aWNlCkVPRgpjaG1vZCAreCAvZXRjL2Nyb24ud2Vla2x5L3B1bGwtaW1hZ2UKCmNhdCA+L2V0Yy9jcm9uLndlZWtseS95dW11cGRhdGUgPDwnRU9GJwojIS9iaW4vYmFzaAoKeXVtIHVwZGF0ZSAteQpFT0YKY2htb2QgK3ggL2V0Yy9jcm9uLndlZWtseS95dW11cGRhdGUKCmNhdCA+L2V0Yy9jcm9uLmRhaWx5L3Jlc3RhcnQtcHJveHkgPDwnRU9GJwojIS9iaW4vYmFzaAoKc3lzdGVtY3RsIHJlc3RhcnQgcHJveHkuc2VydmljZQpFT0YKY2htb2QgK3ggL2V0Yy9jcm9uLmRhaWx5L3Jlc3RhcnQtcHJveHkKCigKCXNsZWVwIDMwCglyZWJvb3QKKSAmCg==')))]" }, "provisionAfterExtensions": [ "Microsoft.Azure.Monitor.AzureMonitorLinuxAgent", diff --git a/pkg/deploy/assets/gateway-production.json b/pkg/deploy/assets/gateway-production.json index 1a3296cd8..19f123da7 100644 --- a/pkg/deploy/assets/gateway-production.json +++ b/pkg/deploy/assets/gateway-production.json @@ -226,7 +226,7 @@ }, "properties": { "upgradePolicy": { - "mode": "Manual" + "mode": "Rolling" }, "virtualMachineProfile": { "osProfile": { @@ -309,7 +309,7 @@ "autoUpgradeMinorVersion": true, "settings": {}, "protectedSettings": { - "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'ACRRESOURCEID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('acrResourceId')),''')\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','DATABASEACCOUNTNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('databaseAccountName')),''')\n','DBTOKENCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenClientId')),''')\n','DBTOKENURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenUrl')),''')\n','MDMFRONTENDURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdmFrontendUrl')),''')\n','MDSDENVIRONMENT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdsdEnvironment')),''')\n','FLUENTBITIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fluentbitImage')),''')\n','GATEWAYMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayMdsdConfigVersion')),''')\n','GATEWAYDOMAINS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayDomains')),''')\n','GATEWAYFEATURES=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayFeatures')),''')\n','KEYVAULTDNSSUFFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultDNSSuffix')),''')\n','KEYVAULTPREFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultPrefix')),''')\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','RPMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdNamespace')),''')\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('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgojQWRkaW5nIHJldHJ5IGxvZ2ljIHRvIHl1bSBjb21tYW5kcyBpbiBvcmRlciB0byBhdm9pZCBzdGFsbGluZyBvdXQgb24gcmVzb3VyY2UgbG9ja3MKZWNobyAicnVubmluZyBSSFVJIGZpeCIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIHl1bSB1cGRhdGUgLXkgLS1kaXNhYmxlcmVwbz0nKicgLS1lbmFibGVyZXBvPSdyaHVpLW1pY3Jvc29mdC1henVyZSonICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gInJ1bm5pbmcgeXVtIHVwZGF0ZSIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIHl1bSAteSAteCBXQUxpbnV4QWdlbnQgLXggV0FMaW51eEFnZW50LXVkZXYgdXBkYXRlIC0tYWxsb3dlcmFzaW5nICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gImV4dGVuZGluZyBwYXJ0aXRpb24gdGFibGUiCiMgTGludXggYmxvY2sgZGV2aWNlcyBhcmUgaW5jb25zaXN0ZW50bHkgbmFtZWQKIyBpdCdzIGRpZmZpY3VsdCB0byB0aWUgdGhlIGx2bSBwdiB0byB0aGUgcGh5c2ljYWwgZGlzayB1c2luZyAvZGV2L2Rpc2sgZmlsZXMsIHdoaWNoIGlzIHdoeSBsdnMgaXMgdXNlZCBoZXJlCnBoeXNpY2FsX2Rpc2s9IiQobHZzIC1vIGRldmljZXMgLWEgfCBoZWFkIC1uMiB8IHRhaWwgLW4xIHwgY3V0IC1kICcgJyAtZiAzIHwgY3V0IC1kIFwoIC1mIDEgfCB0ciAtZCAnWzpkaWdpdDpdJykiCmdyb3dwYXJ0ICIkcGh5c2ljYWxfZGlzayIgMgoKZWNobyAiZXh0ZW5kaW5nIGZpbGVzeXN0ZW1zIgpsdmV4dGVuZCAtbCArMjAlRlJFRSAvZGV2L3Jvb3R2Zy9yb290bHYKeGZzX2dyb3dmcyAvCgpsdmV4dGVuZCAtbCArMTAwJUZSRUUgL2Rldi9yb290dmcvdmFybHYKeGZzX2dyb3dmcyAvdmFyCgpycG0gLS1pbXBvcnQgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9SUE0tR1BHLUtFWS1FUEVMLTgKcnBtIC0taW1wb3J0IGh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS9rZXlzL21pY3Jvc29mdC5hc2MKCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KICB5dW0gLXkgaW5zdGFsbCBodHRwczovL2RsLmZlZG9yYXByb2plY3Qub3JnL3B1Yi9lcGVsL2VwZWwtcmVsZWFzZS1sYXRlc3QtOC5ub2FyY2gucnBtICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gImNvbmZpZ3VyaW5nIGxvZ3JvdGF0ZSIKCiMgZ2F0ZXdheV9sb2dkaXIgaXMgYSByZWFkb25seSB2YXJpYWJsZSB0aGF0IHNwZWNpZmllcyB0aGUgaG9zdCBwYXRoIG1vdW50IHBvaW50IGZvciB0aGUgZ2F0ZXdheSBjb250YWluZXIgbG9nIGZpbGUKIyBmb3IgdGhlIHB1cnBvc2Ugb2Ygcm90YXRpbmcgdGhlIGdhdGV3YXkgbG9ncwpkZWNsYXJlIC1yIGdhdGV3YXlfbG9nZGlyPScvdmFyL2xvZy9hcm8tZ2F0ZXdheScKCmNhdCA+L2V0Yy9sb2dyb3RhdGUuY29uZiA8PEVPRgojIHNlZSAibWFuIGxvZ3JvdGF0ZSIgZm9yIGRldGFpbHMKIyByb3RhdGUgbG9nIGZpbGVzIHdlZWtseQp3ZWVrbHkKCiMga2VlcCAyIHdlZWtzIHdvcnRoIG9mIGJhY2tsb2dzCnJvdGF0ZSAyCgojIGNyZWF0ZSBuZXcgKGVtcHR5KSBsb2cgZmlsZXMgYWZ0ZXIgcm90YXRpbmcgb2xkIG9uZXMKY3JlYXRlCgojIHVzZSBkYXRlIGFzIGEgc3VmZml4IG9mIHRoZSByb3RhdGVkIGZpbGUKZGF0ZWV4dAoKIyB1bmNvbW1lbnQgdGhpcyBpZiB5b3Ugd2FudCB5b3VyIGxvZyBmaWxlcyBjb21wcmVzc2VkCmNvbXByZXNzCgojIFJQTSBwYWNrYWdlcyBkcm9wIGxvZyByb3RhdGlvbiBpbmZvcm1hdGlvbiBpbnRvIHRoaXMgZGlyZWN0b3J5CmluY2x1ZGUgL2V0Yy9sb2dyb3RhdGUuZAoKIyBubyBwYWNrYWdlcyBvd24gd3RtcCBhbmQgYnRtcCAtLSB3ZSdsbCByb3RhdGUgdGhlbSBoZXJlCi92YXIvbG9nL3d0bXAgewogICAgbW9udGhseQogICAgY3JlYXRlIDA2NjQgcm9vdCB1dG1wCiAgICAgICAgbWluc2l6ZSAxTQogICAgcm90YXRlIDEKfQoKL3Zhci9sb2cvYnRtcCB7CiAgICBtaXNzaW5nb2sKICAgIG1vbnRobHkKICAgIGNyZWF0ZSAwNjAwIHJvb3QgdXRtcAogICAgcm90YXRlIDEKfQoKIyBNYXhpbXVtIGxvZyBkaXJlY3Rvcnkgc2l6ZSBpcyAxMDBHIHdpdGggdGhpcyBjb25maWd1cmF0aW9uCiMgU2V0dGluZyBsaW1pdCB0byAxMDBHIHRvIGFsbG93IHNwYWNlIGZvciBvdGhlciBsb2dnaW5nIHNlcnZpY2VzCiMgY29weXRydW5jYXRlIGlzIGEgY3JpdGljYWwgb3B0aW9uIHVzZWQgdG8gcHJldmVudCBsb2dzIGZyb20gYmVpbmcgc2hpcHBlZCB0d2ljZQoke2dhdGV3YXlfbG9nZGlyfSB7CiAgICBzaXplIDIwRwogICAgcm90YXRlIDUKICAgIGNyZWF0ZSAwNjAwIHJvb3Qgcm9vdAogICAgY29weXRydW5jYXRlCiAgICBub29sZGRpcgogICAgY29tcHJlc3MKfQpFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIHl1bSByZXBvc2l0b3J5IGFuZCBydW5uaW5nIHl1bSB1cGRhdGUiCmNhdCA+L2V0Yy95dW0ucmVwb3MuZC9henVyZS5yZXBvIDw8J0VPRicKW2F6dXJlLWNsaV0KbmFtZT1henVyZS1jbGkKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmUtY2xpCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPXllcwoKW2F6dXJlY29yZV0KbmFtZT1henVyZWNvcmUKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmVjb3JlCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPW5vCkVPRgoKc2VtYW5hZ2UgZmNvbnRleHQgLWEgLXQgdmFyX2xvZ190ICIvdmFyL2xvZy9qb3VybmFsKC8uKik/Igpta2RpciAtcCAvdmFyL2xvZy9qb3VybmFsCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNX07IGRvCiAgeXVtIC15IGluc3RhbGwgY2xhbWF2IGF6c2VjLWNsYW1hdiBhenNlYy1tb25pdG9yIGF6dXJlLWNsaSBhenVyZS1tZHNkIGF6dXJlLXNlY3VyaXR5IHBvZG1hbi1kb2NrZXIgb3BlbnNzbC1wZXJsIHB5dGhvbjMgJiYgYnJlYWsKICAjIGhhY2sgLSB3ZSBhcmUgaW5zdGFsbGluZyBweXRob24zIG9uIGhvc3RzIGR1ZSB0byBhbiBpc3N1ZSB3aXRoIEF6dXJlIExpbnV4IEV4dGVuc2lvbnMgaHR0cHM6Ly9naXRodWIuY29tL0F6dXJlL2F6dXJlLWxpbnV4LWV4dGVuc2lvbnMvcHVsbC8xNTA1CiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gImFwcGx5aW5nIGZpcmV3YWxsIHJ1bGVzIgojIGh0dHBzOi8vYWNjZXNzLnJlZGhhdC5jb20vc2VjdXJpdHkvY3ZlL2N2ZS0yMDIwLTEzNDAxCmNhdCA+L2V0Yy9zeXNjdGwuZC8wMi1kaXNhYmxlLWFjY2VwdC1yYS5jb25mIDw8J0VPRicKbmV0LmlwdjYuY29uZi5hbGwuYWNjZXB0X3JhPTAKRU9GCgpjYXQgPi9ldGMvc3lzY3RsLmQvMDEtZGlzYWJsZS1jb3JlLmNvbmYgPDwnRU9GJwprZXJuZWwuY29yZV9wYXR0ZXJuID0gfC9iaW4vdHJ1ZQpFT0YKc3lzY3RsIC0tc3lzdGVtCgpmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD04MC90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9ODA4MS90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9NDQzL3RjcCAtLXBlcm1hbmVudAoKZWNobyAibG9nZ2luZyBpbnRvIHByb2QgYWNyIgpleHBvcnQgQVpVUkVfQ0xPVURfTkFNRT0kQVpVUkVDTE9VRE5BTUUKYXogbG9naW4gLWkgLS1hbGxvdy1uby1zdWJzY3JpcHRpb25zCgojIFRoZSBtYW5hZ2VkIGlkZW50aXR5IHRoYXQgdGhlIFZNIHJ1bnMgYXMgb25seSBoYXMgYSBzaW5nbGUgcm9sZWFzc2lnbm1lbnQuCiMgVGhpcyByb2xlIGFzc2lnbm1lbnQgaXMgQUNSUHVsbCB3aGljaCBpcyBub3QgbmVjZXNzYXJpbHkgcHJlc2VudCBpbiB0aGUKIyBzdWJzY3JpcHRpb24gd2UncmUgZGVwbG95aW5nIGludG8uICBJZiB0aGUgaWRlbnRpdHkgZG9lcyBub3QgaGF2ZSBhbnkKIyByb2xlIGFzc2lnbm1lbnRzIHNjb3BlZCBvbiB0aGUgc3Vic2NyaXB0aW9uIHdlJ3JlIGRlcGxveWluZyBpbnRvLCBpdCB3aWxsCiMgbm90IHNob3cgb24gYXogbG9naW4gLWksIHdoaWNoIGlzIHdoeSB0aGUgYmVsb3cgbGluZSBpcyBjb21tZW50ZWQuCiMgYXogYWNjb3VudCBzZXQgLXMgIiRTVUJTQ1JJUFRJT05JRCIKCiMgU3VwcHJlc3MgZW11bGF0aW9uIG91dHB1dCBmb3IgcG9kbWFuIGluc3RlYWQgb2YgZG9ja2VyIGZvciBheiBhY3IgY29tcGF0YWJpbGl0eQpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKbWtkaXIgLXAgL3Jvb3QvLmRvY2tlcgpSRUdJU1RSWV9BVVRIX0ZJTEU9L3Jvb3QvLmRvY2tlci9jb25maWcuanNvbiBheiBhY3IgbG9naW4gLS1uYW1lICIkKHNlZCAtZSAnc3wuKi98fCcgPDw8IiRBQ1JSRVNPVVJDRUlEIikiCgpNRE1JTUFHRT0iJHtSUElNQUdFJSUvKn0vJHtNRE1JTUFHRSMjKi99Igpkb2NrZXIgcHVsbCAiJE1ETUlNQUdFIgpkb2NrZXIgcHVsbCAiJFJQSU1BR0UiCmRvY2tlciBwdWxsICIkRkxVRU5UQklUSU1BR0UiCgpheiBsb2dvdXQKCmVjaG8gImNvbmZpZ3VyaW5nIGZsdWVudGJpdCBzZXJ2aWNlIgpta2RpciAtcCAvZXRjL2ZsdWVudGJpdC8KbWtkaXIgLXAgL3Zhci9saWIvZmx1ZW50CgpjYXQgPi9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mIDw8J0VPRicKW0lOUFVUXQoJTmFtZSBzeXN0ZW1kCglUYWcgam91cm5hbGQKCVN5c3RlbWRfRmlsdGVyIF9DT01NPWFybwoJREIgL3Zhci9saWIvZmx1ZW50L2pvdXJuYWxkYgoKW0ZJTFRFUl0KCU5hbWUgbW9kaWZ5CglNYXRjaCBqb3VybmFsZAoJUmVtb3ZlX3dpbGRjYXJkIF8KCVJlbW92ZSBUSU1FU1RBTVAKCltPVVRQVVRdCglOYW1lIGZvcndhcmQKCU1hdGNoICoKCVBvcnQgMjkyMzAKRU9GCgplY2hvICJGTFVFTlRCSVRJTUFHRT0kRkxVRU5UQklUSU1BR0UiID4vZXRjL3N5c2NvbmZpZy9mbHVlbnRiaXQKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9mbHVlbnRiaXQuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0ClN0YXJ0TGltaXRJbnRlcnZhbFNlYz0wCgpbU2VydmljZV0KUmVzdGFydFNlYz0xcwpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvZmx1ZW50Yml0CkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLXNlY3VyaXR5LW9wdCBsYWJlbD1kaXNhYmxlIFwKICAtLWVudHJ5cG9pbnQgL29wdC90ZC1hZ2VudC1iaXQvYmluL3RkLWFnZW50LWJpdCBcCiAgLS1uZXQ9aG9zdCBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC12IC9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mOi9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mIFwKICAtdiAvdmFyL2xpYi9mbHVlbnQ6L3Zhci9saWIvZmx1ZW50OnogXAogIC12IC92YXIvbG9nL2pvdXJuYWw6L3Zhci9sb2cvam91cm5hbDpybyBcCiAgLXYgL2V0Yy9tYWNoaW5lLWlkOi9ldGMvbWFjaGluZS1pZDpybyBcCiAgJEZMVUVOVEJJVElNQUdFIFwKICAtYyAvZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZgoKRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgJU4KUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz01ClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBtZG0gc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9tZG0gPDxFT0YKTURNRlJPTlRFTkRVUkw9JyRNRE1GUk9OVEVORFVSTCcKTURNSU1BR0U9JyRNRE1JTUFHRScKTURNU09VUkNFRU5WSVJPTk1FTlQ9JyRMT0NBVElPTicKTURNU09VUkNFUk9MRT1nYXRld2F5Ck1ETVNPVVJDRVJPTEVJTlNUQU5DRT0nJChob3N0bmFtZSknCkVPRgoKbWtkaXIgL3Zhci9ldHcKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL21kbS5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvbWRtCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWVudHJ5cG9pbnQgL3Vzci9zYmluL01ldHJpY3NFeHRlbnNpb24gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtbSAyZyBcCiAgLXYgL2V0Yy9tZG0ucGVtOi9ldGMvbWRtLnBlbSBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJE1ETUlNQUdFIFwKICAtQ2VydEZpbGUgL2V0Yy9tZG0ucGVtIFwKICAtRnJvbnRFbmRVcmwgJE1ETUZST05URU5EVVJMIFwKICAtTG9nZ2VyIENvbnNvbGUgXAogIC1Mb2dMZXZlbCBXYXJuaW5nIFwKICAtUHJpdmF0ZUtleUZpbGUgL2V0Yy9tZG0ucGVtIFwKICAtU291cmNlRW52aXJvbm1lbnQgJE1ETVNPVVJDRUVOVklST05NRU5UIFwKICAtU291cmNlUm9sZSAkTURNU09VUkNFUk9MRSBcCiAgLVNvdXJjZVJvbGVJbnN0YW5jZSAkTURNU09VUkNFUk9MRUlOU1RBTkNFCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVOClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKZWNobyAiY29uZmlndXJpbmcgYXJvLWdhdGV3YXkgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tZ2F0ZXdheSA8PEVPRgpBQ1JfUkVTT1VSQ0VfSUQ9JyRBQ1JSRVNPVVJDRUlEJwpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpBWlVSRV9EQlRPS0VOX0NMSUVOVF9JRD0nJERCVE9LRU5DTElFTlRJRCcKREJUT0tFTl9VUkw9JyREQlRPS0VOVVJMJwpNRE1fQUNDT1VOVD0iJFJQTURNQUNDT1VOVCIKTURNX05BTUVTUEFDRT1HYXRld2F5CkdBVEVXQVlfRE9NQUlOUz0nJEdBVEVXQVlET01BSU5TJwpHQVRFV0FZX0ZFQVRVUkVTPSckR0FURVdBWUZFQVRVUkVTJwpSUElNQUdFPSckUlBJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vYXJvLWdhdGV3YXkuc2VydmljZSA8PEVPRgpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tZ2F0ZXdheQpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnRQcmU9L3Vzci9iaW4vbWtkaXIgLXAgJHtnYXRld2F5X2xvZ2Rpcn0KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtZSBBQ1JfUkVTT1VSQ0VfSUQgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgQVpVUkVfREJUT0tFTl9DTElFTlRfSUQgXAogIC1lIERCVE9LRU5fVVJMIFwKICAtZSBHQVRFV0FZX0RPTUFJTlMgXAogIC1lIEdBVEVXQVlfRkVBVFVSRVMgXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtbSAyZyBcCiAgLXAgODA6ODA4MCBcCiAgLXAgODA4MTo4MDgxIFwKICAtcCA0NDM6ODQ0MyBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogIC12ICR7Z2F0ZXdheV9sb2dkaXJ9Oi9jdHIubG9nOnogXAogIFwkUlBJTUFHRSBcCiAgZ2F0ZXdheQpFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAtdCAzNjAwICVOClRpbWVvdXRTdG9wU2VjPTM2MDAKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgpjaGNvbiAtUiBzeXN0ZW1fdTpvYmplY3Rfcjp2YXJfbG9nX3Q6czAgL3Zhci9vcHQvbWljcm9zb2Z0L2xpbnV4bW9uYWdlbnQKCm1rZGlyIC1wIC92YXIvbGliL3dhYWdlbnQvTWljcm9zb2Z0LkF6dXJlLktleVZhdWx0LlN0b3JlCgplY2hvICJjb25maWd1cmluZyBtZHNkIGFuZCBtZG0gc2VydmljZXMiCmZvciB2YXIgaW4gIm1kc2QiICJtZG0iOyBkbwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy5zZXJ2aWNlIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKCltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoICR2YXIKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy50aW1lciA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249UGVyaW9kaWMgJHZhciBjcmVkZW50aWFscyByZWZyZXNoCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltUaW1lcl0KT25Cb290U2VjPTBtaW4KT25DYWxlbmRhcj0wLzEyOjAwOjAwCkFjY3VyYWN5U2VjPTVzCgpbSW5zdGFsbF0KV2FudGVkQnk9dGltZXJzLnRhcmdldApFT0YKZG9uZQoKY2F0ID4vdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCA8PEVPRgojIS9iaW4vYmFzaApzZXQgLWV1CgpDT01QT05FTlQ9IlwkMSIKZWNobyAiRG93bmxvYWQgXCRDT01QT05FTlQgY3JlZGVudGlhbHMiCgpURU1QX0RJUj1cJChta3RlbXAgLWQpCmV4cG9ydCBBWlVSRV9DT05GSUdfRElSPVwkKG1rdGVtcCAtZCkKCmVjaG8gIkxvZ2dpbmcgaW50byBBenVyZS4uLiIKUkVUUklFUz0zCndoaWxlIFsgIlwkUkVUUklFUyIgLWd0IDAgXTsgZG8KICAgIGlmIGF6IGxvZ2luIC1pIC0tYWxsb3ctbm8tc3Vic2NyaXB0aW9ucwogICAgdGhlbgogICAgICAgIGVjaG8gImF6IGxvZ2luIHN1Y2Nlc3NmdWwiCiAgICAgICAgYnJlYWsKICAgIGVsc2UKICAgICAgICBlY2hvICJheiBsb2dpbiBmYWlsZWQuIFJldHJ5aW5nLi4uIgogICAgICAgIGxldCBSRVRSSUVTLT0xCiAgICAgICAgc2xlZXAgNQogICAgZmkKZG9uZQoKdHJhcCAiY2xlYW51cCIgRVhJVAoKY2xlYW51cCgpIHsKICBheiBsb2dvdXQKICBbWyAiXCRURU1QX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRURU1QX0RJUgogIFtbICJcJEFaVVJFX0NPTkZJR19ESVIiID1+IC90bXAvLisgXV0gJiYgcm0gLXJmIFwkQVpVUkVfQ09ORklHX0RJUgp9CgppZiBbICJcJENPTVBPTkVOVCIgPSAibWRtIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii9ldGMvbWRtLnBlbSIKZWxpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogIENVUlJFTlRfQ0VSVF9GSUxFPSIvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSIKZWxzZQogIGVjaG8gSW52YWxpZCB1c2FnZSAmJiBleGl0IDEKZmkKClNFQ1JFVF9OQU1FPSJnd3ktXCR7Q09NUE9ORU5UfSIKTkVXX0NFUlRfRklMRT0iXCRURU1QX0RJUi9cJENPTVBPTkVOVC5wZW0iCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KICBheiBrZXl2YXVsdCBzZWNyZXQgZG93bmxvYWQgLS1maWxlIFwkTkVXX0NFUlRfRklMRSAtLWlkICJodHRwczovLyRLRVlWQVVMVFBSRUZJWC1nd3kuJEtFWVZBVUxURE5TU1VGRklYL3NlY3JldHMvXCRTRUNSRVRfTkFNRSIgJiYgYnJlYWsKICBpZiBbWyBcJGF0dGVtcHQgLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgppZiBbIC1mIFwkTkVXX0NFUlRfRklMRSBdOyB0aGVuCiAgaWYgWyAiXCRDT01QT05FTlQiID0gIm1kc2QiIF07IHRoZW4KICAgIGNob3duIHN5c2xvZzpzeXNsb2cgXCRORVdfQ0VSVF9GSUxFCiAgZWxzZQogICAgc2VkIC1pIC1uZSAnMSwvRU5EIENFUlRJRklDQVRFLyBwJyBcJE5FV19DRVJUX0ZJTEUKICBmaQoKICBuZXdfY2VydF9zbj0iXCQob3BlbnNzbCB4NTA5IC1pbiAiXCRORVdfQ0VSVF9GSUxFIiAtbm9vdXQgLXNlcmlhbCB8IGF3ayAtRj0gJ3twcmludCBcJDJ9JykiCiAgY3VycmVudF9jZXJ0X3NuPSJcJChvcGVuc3NsIHg1MDkgLWluICJcJENVUlJFTlRfQ0VSVF9GSUxFIiAtbm9vdXQgLXNlcmlhbCB8IGF3ayAtRj0gJ3twcmludCBcJDJ9JykiCiAgaWYgW1sgISAteiBcJG5ld19jZXJ0X3NuIF1dICYmIFtbIFwkbmV3X2NlcnRfc24gIT0gIlwkY3VycmVudF9jZXJ0X3NuIiBdXTsgdGhlbgogICAgZWNobyB1cGRhdGluZyBjZXJ0aWZpY2F0ZSBmb3IgXCRDT01QT05FTlQKICAgIGNobW9kIDA2MDAgXCRORVdfQ0VSVF9GSUxFCiAgICBtdiBcJE5FV19DRVJUX0ZJTEUgXCRDVVJSRU5UX0NFUlRfRklMRQogIGZpCmVsc2UKICBlY2hvIEZhaWxlZCB0byByZWZyZXNoIGNlcnRpZmljYXRlIGZvciBcJENPTVBPTkVOVCAmJiBleGl0IDEKZmkKRU9GCgpjaG1vZCB1K3ggL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2gKCnN5c3RlbWN0bCBlbmFibGUgZG93bmxvYWQtbWRzZC1jcmVkZW50aWFscy50aW1lcgpzeXN0ZW1jdGwgZW5hYmxlIGRvd25sb2FkLW1kbS1jcmVkZW50aWFscy50aW1lcgoKL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggbWRzZAovdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCBtZG0KTURTRENFUlRJRklDQVRFU0FOPSQob3BlbnNzbCB4NTA5IC1pbiAvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSAtbm9vdXQgLXN1YmplY3QgfCBzZWQgLWUgJ3MvLipDTiA9IC8vJykKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS93YXRjaC1tZG0tY3JlZGVudGlhbHMuc2VydmljZSA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249V2F0Y2ggZm9yIGNoYW5nZXMgaW4gbWRtLnBlbSBhbmQgcmVzdGFydHMgdGhlIG1kbSBzZXJ2aWNlCgpbU2VydmljZV0KVHlwZT1vbmVzaG90CkV4ZWNTdGFydD0vdXNyL2Jpbi9zeXN0ZW1jdGwgcmVzdGFydCBtZG0uc2VydmljZQoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL3dhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoIDw8RU9GCltQYXRoXQpQYXRoTW9kaWZpZWQ9L2V0Yy9tZG0ucGVtCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgpzeXN0ZW1jdGwgZW5hYmxlIHdhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoCnN5c3RlbWN0bCBzdGFydCB3YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aAoKbWtkaXIgL2V0Yy9zeXN0ZW1kL3N5c3RlbS9tZHNkLnNlcnZpY2UuZApjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRzZC5zZXJ2aWNlLmQvb3ZlcnJpZGUuY29uZiA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKRU9GCgpjYXQgPi9ldGMvZGVmYXVsdC9tZHNkIDw8RU9GCk1EU0RfUk9MRV9QUkVGSVg9L3Zhci9ydW4vbWRzZC9kZWZhdWx0Ck1EU0RfT1BUSU9OUz0iLUEgLWQgLXIgXCRNRFNEX1JPTEVfUFJFRklYIgoKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0VOVklST05NRU5UPSckTURTREVOVklST05NRU5UJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfQUNDT1VOVD0nJFJQTURTREFDQ09VTlQnCmV4cG9ydCBNT05JVE9SSU5HX0dDU19SRUdJT049JyRMT0NBVElPTicKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FVVEhfSURfVFlQRT1BdXRoS2V5VmF1bHQKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FVVEhfSUQ9JyRNRFNEQ0VSVElGSUNBVEVTQU4nCmV4cG9ydCBNT05JVE9SSU5HX0dDU19OQU1FU1BBQ0U9JyRSUE1EU0ROQU1FU1BBQ0UnCmV4cG9ydCBNT05JVE9SSU5HX0NPTkZJR19WRVJTSU9OPSckR0FURVdBWU1EU0RDT05GSUdWRVJTSU9OJwpleHBvcnQgTU9OSVRPUklOR19VU0VfR0VORVZBX0NPTkZJR19TRVJWSUNFPXRydWUKCmV4cG9ydCBNT05JVE9SSU5HX1RFTkFOVD0nJExPQ0FUSU9OJwpleHBvcnQgTU9OSVRPUklOR19ST0xFPWdhdGV3YXkKZXhwb3J0IE1PTklUT1JJTkdfUk9MRV9JTlNUQU5DRT0nJChob3N0bmFtZSknCgpleHBvcnQgTURTRF9NU0dQQUNLX1NPUlRfQ09MVU1OUz0xCkVPRgoKIyBzZXR0aW5nIE1PTklUT1JJTkdfR0NTX0FVVEhfSURfVFlQRT1BdXRoS2V5VmF1bHQgc2VlbXMgdG8gaGF2ZSBjYXVzZWQgbWRzZCBub3QKIyB0byBob25vdXIgU1NMX0NFUlRfRklMRSBhbnkgbW9yZSwgaGVhdmVuIG9ubHkga25vd3Mgd2h5Lgpta2RpciAtcCAvdXNyL2xpYi9zc2wvY2VydHMKY3NwbGl0IC1mIC91c3IvbGliL3NzbC9jZXJ0cy9jZXJ0LSAtYiAlMDNkLnBlbSAvZXRjL3BraS90bHMvY2VydHMvY2EtYnVuZGxlLmNydCAvXiQvMSB7Kn0gPi9kZXYvbnVsbApjX3JlaGFzaCAvdXNyL2xpYi9zc2wvY2VydHMKCiMgd2UgbGVhdmUgY2xpZW50SWQgYmxhbmsgYXMgbG9uZyBhcyBvbmx5IDEgbWFuYWdlZCBpZGVudGl0eSBhc3NpZ25lZCB0byB2bXNzCiMgaWYgd2UgaGF2ZSBtb3JlIHRoYW4gMSwgd2Ugd2lsbCBuZWVkIHRvIHBvcHVsYXRlIHdpdGggY2xpZW50SWQgdXNlZCBmb3Igb2ZmLW5vZGUgc2Nhbm5pbmcKY2F0ID4vZXRjL2RlZmF1bHQvdnNhLW5vZGVzY2FuLWFnZW50LmNvbmZpZyA8PEVPRgp7CiAgICAiTmljZSI6IDE5LAogICAgIlRpbWVvdXQiOiAxMDgwMCwKICAgICJDbGllbnRJZCI6ICIiLAogICAgIlRlbmFudElkIjogIiRBWlVSRVNFQ1BBQ0tWU0FURU5BTlRJRCIsCiAgICAiUXVhbHlzU3RvcmVCYXNlVXJsIjogIiRBWlVSRVNFQ1BBQ0tRVUFMWVNVUkwiLAogICAgIlByb2Nlc3NUaW1lb3V0IjogMzAwLAogICAgIkNvbW1hbmREZWxheSI6IDAKICB9CkVPRgoKZWNobyAiZW5hYmxpbmcgYXJvIHNlcnZpY2VzIgpmb3Igc2VydmljZSBpbiBhcm8tZ2F0ZXdheSBhdW9tcyBhenNlY2QgYXpzZWNtb25kIG1kc2QgbWRtIGNocm9ueWQgZmx1ZW50Yml0OyBkbwogIHN5c3RlbWN0bCBlbmFibGUgJHNlcnZpY2Uuc2VydmljZQpkb25lCgpmb3Igc2NhbiBpbiBiYXNlbGluZSBjbGFtYXYgc29mdHdhcmU7IGRvCiAgL3Vzci9sb2NhbC9iaW4vYXpzZWNkIGNvbmZpZyAtcyAkc2NhbiAtZCBQMUQKZG9uZQoKZWNobyAicmVib290aW5nIgpyZXN0b3JlY29uIC1SRiAvdmFyL2xvZy8qCihzbGVlcCAzMDsgcmVib290KSAmCg==')))]" + "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'ACRRESOURCEID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('acrResourceId')),''')\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','DATABASEACCOUNTNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('databaseAccountName')),''')\n','DBTOKENCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenClientId')),''')\n','DBTOKENURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenUrl')),''')\n','MDMFRONTENDURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdmFrontendUrl')),''')\n','MDSDENVIRONMENT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdsdEnvironment')),''')\n','FLUENTBITIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fluentbitImage')),''')\n','GATEWAYMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayMdsdConfigVersion')),''')\n','GATEWAYDOMAINS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayDomains')),''')\n','GATEWAYFEATURES=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayFeatures')),''')\n','KEYVAULTDNSSUFFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultDNSSuffix')),''')\n','KEYVAULTPREFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultPrefix')),''')\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','RPMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdNamespace')),''')\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('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgojQWRkaW5nIHJldHJ5IGxvZ2ljIHRvIHl1bSBjb21tYW5kcyBpbiBvcmRlciB0byBhdm9pZCBzdGFsbGluZyBvdXQgb24gcmVzb3VyY2UgbG9ja3MKZWNobyAicnVubmluZyBSSFVJIGZpeCIKZm9yIGF0dGVtcHQgaW4gezEuLjYwfTsgZG8KICB5dW0gdXBkYXRlIC15IC0tZGlzYWJsZXJlcG89JyonIC0tZW5hYmxlcmVwbz0ncmh1aS1taWNyb3NvZnQtYXp1cmUqJyAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAicnVubmluZyB5dW0gdXBkYXRlIgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwogIHl1bSAteSAteCBXQUxpbnV4QWdlbnQgLXggV0FMaW51eEFnZW50LXVkZXYgdXBkYXRlIC0tYWxsb3dlcmFzaW5nICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNjAgXV07IHRoZW4gc2xlZXAgMzA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJleHRlbmRpbmcgcGFydGl0aW9uIHRhYmxlIgojIExpbnV4IGJsb2NrIGRldmljZXMgYXJlIGluY29uc2lzdGVudGx5IG5hbWVkCiMgaXQncyBkaWZmaWN1bHQgdG8gdGllIHRoZSBsdm0gcHYgdG8gdGhlIHBoeXNpY2FsIGRpc2sgdXNpbmcgL2Rldi9kaXNrIGZpbGVzLCB3aGljaCBpcyB3aHkgbHZzIGlzIHVzZWQgaGVyZQpwaHlzaWNhbF9kaXNrPSIkKGx2cyAtbyBkZXZpY2VzIC1hIHwgaGVhZCAtbjIgfCB0YWlsIC1uMSB8IGN1dCAtZCAnICcgLWYgMyB8IGN1dCAtZCBcKCAtZiAxIHwgdHIgLWQgJ1s6ZGlnaXQ6XScpIgpncm93cGFydCAiJHBoeXNpY2FsX2Rpc2siIDIKCmVjaG8gImV4dGVuZGluZyBmaWxlc3lzdGVtcyIKbHZleHRlbmQgLWwgKzIwJUZSRUUgL2Rldi9yb290dmcvcm9vdGx2Cnhmc19ncm93ZnMgLwoKbHZleHRlbmQgLWwgKzEwMCVGUkVFIC9kZXYvcm9vdHZnL3Zhcmx2Cnhmc19ncm93ZnMgL3ZhcgoKcnBtIC0taW1wb3J0IGh0dHBzOi8vZGwuZmVkb3JhcHJvamVjdC5vcmcvcHViL2VwZWwvUlBNLUdQRy1LRVktRVBFTC04CnJwbSAtLWltcG9ydCBodHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20va2V5cy9taWNyb3NvZnQuYXNjCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwogIHl1bSAteSBpbnN0YWxsIGh0dHBzOi8vZGwuZmVkb3JhcHJvamVjdC5vcmcvcHViL2VwZWwvZXBlbC1yZWxlYXNlLWxhdGVzdC04Lm5vYXJjaC5ycG0gJiYgYnJlYWsKICBpZiBbWyAke2F0dGVtcHR9IC1sdCA2MCBdXTsgdGhlbiBzbGVlcCAzMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gImNvbmZpZ3VyaW5nIGxvZ3JvdGF0ZSIKCiMgZ2F0ZXdheV9sb2dkaXIgaXMgYSByZWFkb25seSB2YXJpYWJsZSB0aGF0IHNwZWNpZmllcyB0aGUgaG9zdCBwYXRoIG1vdW50IHBvaW50IGZvciB0aGUgZ2F0ZXdheSBjb250YWluZXIgbG9nIGZpbGUKIyBmb3IgdGhlIHB1cnBvc2Ugb2Ygcm90YXRpbmcgdGhlIGdhdGV3YXkgbG9ncwpkZWNsYXJlIC1yIGdhdGV3YXlfbG9nZGlyPScvdmFyL2xvZy9hcm8tZ2F0ZXdheScKCmNhdCA+L2V0Yy9sb2dyb3RhdGUuY29uZiA8PEVPRgojIHNlZSAibWFuIGxvZ3JvdGF0ZSIgZm9yIGRldGFpbHMKIyByb3RhdGUgbG9nIGZpbGVzIHdlZWtseQp3ZWVrbHkKCiMga2VlcCAyIHdlZWtzIHdvcnRoIG9mIGJhY2tsb2dzCnJvdGF0ZSAyCgojIGNyZWF0ZSBuZXcgKGVtcHR5KSBsb2cgZmlsZXMgYWZ0ZXIgcm90YXRpbmcgb2xkIG9uZXMKY3JlYXRlCgojIHVzZSBkYXRlIGFzIGEgc3VmZml4IG9mIHRoZSByb3RhdGVkIGZpbGUKZGF0ZWV4dAoKIyB1bmNvbW1lbnQgdGhpcyBpZiB5b3Ugd2FudCB5b3VyIGxvZyBmaWxlcyBjb21wcmVzc2VkCmNvbXByZXNzCgojIFJQTSBwYWNrYWdlcyBkcm9wIGxvZyByb3RhdGlvbiBpbmZvcm1hdGlvbiBpbnRvIHRoaXMgZGlyZWN0b3J5CmluY2x1ZGUgL2V0Yy9sb2dyb3RhdGUuZAoKIyBubyBwYWNrYWdlcyBvd24gd3RtcCBhbmQgYnRtcCAtLSB3ZSdsbCByb3RhdGUgdGhlbSBoZXJlCi92YXIvbG9nL3d0bXAgewogICAgbW9udGhseQogICAgY3JlYXRlIDA2NjQgcm9vdCB1dG1wCiAgICAgICAgbWluc2l6ZSAxTQogICAgcm90YXRlIDEKfQoKL3Zhci9sb2cvYnRtcCB7CiAgICBtaXNzaW5nb2sKICAgIG1vbnRobHkKICAgIGNyZWF0ZSAwNjAwIHJvb3QgdXRtcAogICAgcm90YXRlIDEKfQoKIyBNYXhpbXVtIGxvZyBkaXJlY3Rvcnkgc2l6ZSBpcyAxMDBHIHdpdGggdGhpcyBjb25maWd1cmF0aW9uCiMgU2V0dGluZyBsaW1pdCB0byAxMDBHIHRvIGFsbG93IHNwYWNlIGZvciBvdGhlciBsb2dnaW5nIHNlcnZpY2VzCiMgY29weXRydW5jYXRlIGlzIGEgY3JpdGljYWwgb3B0aW9uIHVzZWQgdG8gcHJldmVudCBsb2dzIGZyb20gYmVpbmcgc2hpcHBlZCB0d2ljZQoke2dhdGV3YXlfbG9nZGlyfSB7CiAgICBzaXplIDIwRwogICAgcm90YXRlIDUKICAgIGNyZWF0ZSAwNjAwIHJvb3Qgcm9vdAogICAgY29weXRydW5jYXRlCiAgICBub29sZGRpcgogICAgY29tcHJlc3MKfQpFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIHl1bSByZXBvc2l0b3J5IGFuZCBydW5uaW5nIHl1bSB1cGRhdGUiCmNhdCA+L2V0Yy95dW0ucmVwb3MuZC9henVyZS5yZXBvIDw8J0VPRicKW2F6dXJlLWNsaV0KbmFtZT1henVyZS1jbGkKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmUtY2xpCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPXllcwoKW2F6dXJlY29yZV0KbmFtZT1henVyZWNvcmUKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmVjb3JlCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPW5vCkVPRgoKc2VtYW5hZ2UgZmNvbnRleHQgLWEgLXQgdmFyX2xvZ190ICIvdmFyL2xvZy9qb3VybmFsKC8uKik/Igpta2RpciAtcCAvdmFyL2xvZy9qb3VybmFsCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNjB9OyBkbwogIHl1bSAteSBpbnN0YWxsIGNsYW1hdiBhenNlYy1jbGFtYXYgYXpzZWMtbW9uaXRvciBhenVyZS1jbGkgYXp1cmUtbWRzZCBhenVyZS1zZWN1cml0eSBwb2RtYW4tZG9ja2VyIG9wZW5zc2wtcGVybCBweXRob24zICYmIGJyZWFrCiAgIyBoYWNrIC0gd2UgYXJlIGluc3RhbGxpbmcgcHl0aG9uMyBvbiBob3N0cyBkdWUgdG8gYW4gaXNzdWUgd2l0aCBBenVyZSBMaW51eCBFeHRlbnNpb25zIGh0dHBzOi8vZ2l0aHViLmNvbS9BenVyZS9henVyZS1saW51eC1leHRlbnNpb25zL3B1bGwvMTUwNQogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDYwIF1dOyB0aGVuIHNsZWVwIDMwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAiYXBwbHlpbmcgZmlyZXdhbGwgcnVsZXMiCiMgaHR0cHM6Ly9hY2Nlc3MucmVkaGF0LmNvbS9zZWN1cml0eS9jdmUvY3ZlLTIwMjAtMTM0MDEKY2F0ID4vZXRjL3N5c2N0bC5kLzAyLWRpc2FibGUtYWNjZXB0LXJhLmNvbmYgPDwnRU9GJwpuZXQuaXB2Ni5jb25mLmFsbC5hY2NlcHRfcmE9MApFT0YKCmNhdCA+L2V0Yy9zeXNjdGwuZC8wMS1kaXNhYmxlLWNvcmUuY29uZiA8PCdFT0YnCmtlcm5lbC5jb3JlX3BhdHRlcm4gPSB8L2Jpbi90cnVlCkVPRgpzeXNjdGwgLS1zeXN0ZW0KCmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTgwL3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD04MDgxL3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDMvdGNwIC0tcGVybWFuZW50CgplY2hvICJsb2dnaW5nIGludG8gcHJvZCBhY3IiCmV4cG9ydCBBWlVSRV9DTE9VRF9OQU1FPSRBWlVSRUNMT1VETkFNRQpheiBsb2dpbiAtaSAtLWFsbG93LW5vLXN1YnNjcmlwdGlvbnMKCiMgVGhlIG1hbmFnZWQgaWRlbnRpdHkgdGhhdCB0aGUgVk0gcnVucyBhcyBvbmx5IGhhcyBhIHNpbmdsZSByb2xlYXNzaWdubWVudC4KIyBUaGlzIHJvbGUgYXNzaWdubWVudCBpcyBBQ1JQdWxsIHdoaWNoIGlzIG5vdCBuZWNlc3NhcmlseSBwcmVzZW50IGluIHRoZQojIHN1YnNjcmlwdGlvbiB3ZSdyZSBkZXBsb3lpbmcgaW50by4gIElmIHRoZSBpZGVudGl0eSBkb2VzIG5vdCBoYXZlIGFueQojIHJvbGUgYXNzaWdubWVudHMgc2NvcGVkIG9uIHRoZSBzdWJzY3JpcHRpb24gd2UncmUgZGVwbG95aW5nIGludG8sIGl0IHdpbGwKIyBub3Qgc2hvdyBvbiBheiBsb2dpbiAtaSwgd2hpY2ggaXMgd2h5IHRoZSBiZWxvdyBsaW5lIGlzIGNvbW1lbnRlZC4KIyBheiBhY2NvdW50IHNldCAtcyAiJFNVQlNDUklQVElPTklEIgoKIyBTdXBwcmVzcyBlbXVsYXRpb24gb3V0cHV0IGZvciBwb2RtYW4gaW5zdGVhZCBvZiBkb2NrZXIgZm9yIGF6IGFjciBjb21wYXRhYmlsaXR5Cm1rZGlyIC1wIC9ldGMvY29udGFpbmVycy8KdG91Y2ggL2V0Yy9jb250YWluZXJzL25vZG9ja2VyCgpta2RpciAtcCAvcm9vdC8uZG9ja2VyClJFR0lTVFJZX0FVVEhfRklMRT0vcm9vdC8uZG9ja2VyL2NvbmZpZy5qc29uIGF6IGFjciBsb2dpbiAtLW5hbWUgIiQoc2VkIC1lICdzfC4qL3x8JyA8PDwiJEFDUlJFU09VUkNFSUQiKSIKCk1ETUlNQUdFPSIke1JQSU1BR0UlJS8qfS8ke01ETUlNQUdFIyMqL30iCmRvY2tlciBwdWxsICIkTURNSU1BR0UiCmRvY2tlciBwdWxsICIkUlBJTUFHRSIKZG9ja2VyIHB1bGwgIiRGTFVFTlRCSVRJTUFHRSIKCmF6IGxvZ291dAoKZWNobyAiY29uZmlndXJpbmcgZmx1ZW50Yml0IHNlcnZpY2UiCm1rZGlyIC1wIC9ldGMvZmx1ZW50Yml0Lwpta2RpciAtcCAvdmFyL2xpYi9mbHVlbnQKCmNhdCA+L2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYgPDwnRU9GJwpbSU5QVVRdCglOYW1lIHN5c3RlbWQKCVRhZyBqb3VybmFsZAoJU3lzdGVtZF9GaWx0ZXIgX0NPTU09YXJvCglEQiAvdmFyL2xpYi9mbHVlbnQvam91cm5hbGRiCgpbRklMVEVSXQoJTmFtZSBtb2RpZnkKCU1hdGNoIGpvdXJuYWxkCglSZW1vdmVfd2lsZGNhcmQgXwoJUmVtb3ZlIFRJTUVTVEFNUAoKW09VVFBVVF0KCU5hbWUgZm9yd2FyZAoJTWF0Y2ggKgoJUG9ydCAyOTIzMApFT0YKCmVjaG8gIkZMVUVOVEJJVElNQUdFPSRGTFVFTlRCSVRJTUFHRSIgPi9ldGMvc3lzY29uZmlnL2ZsdWVudGJpdAoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2ZsdWVudGJpdC5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKU3RhcnRMaW1pdEludGVydmFsU2VjPTAKCltTZXJ2aWNlXQpSZXN0YXJ0U2VjPTFzCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9mbHVlbnRiaXQKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tc2VjdXJpdHktb3B0IGxhYmVsPWRpc2FibGUgXAogIC0tZW50cnlwb2ludCAvb3B0L3RkLWFnZW50LWJpdC9iaW4vdGQtYWdlbnQtYml0IFwKICAtLW5ldD1ob3N0IFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLXYgL2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmY6L2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYgXAogIC12IC92YXIvbGliL2ZsdWVudDovdmFyL2xpYi9mbHVlbnQ6eiBcCiAgLXYgL3Zhci9sb2cvam91cm5hbDovdmFyL2xvZy9qb3VybmFsOnJvIFwKICAtdiAvZXRjL21hY2hpbmUtaWQ6L2V0Yy9tYWNoaW5lLWlkOnJvIFwKICAkRkxVRU5UQklUSU1BR0UgXAogIC1jIC9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mCgpFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAlTgpSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTUKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIG1kbSBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL21kbSA8PEVPRgpNRE1GUk9OVEVORFVSTD0nJE1ETUZST05URU5EVVJMJwpNRE1JTUFHRT0nJE1ETUlNQUdFJwpNRE1TT1VSQ0VFTlZJUk9OTUVOVD0nJExPQ0FUSU9OJwpNRE1TT1VSQ0VST0xFPWdhdGV3YXkKTURNU09VUkNFUk9MRUlOU1RBTkNFPSckKGhvc3RuYW1lKScKRU9GCgpta2RpciAvdmFyL2V0dwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRtLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9tZG0KRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tZW50cnlwb2ludCAvdXNyL3NiaW4vTWV0cmljc0V4dGVuc2lvbiBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1tIDJnIFwKICAtdiAvZXRjL21kbS5wZW06L2V0Yy9tZG0ucGVtIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkTURNSU1BR0UgXAogIC1DZXJ0RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Gcm9udEVuZFVybCAkTURNRlJPTlRFTkRVUkwgXAogIC1Mb2dnZXIgQ29uc29sZSBcCiAgLUxvZ0xldmVsIFdhcm5pbmcgXAogIC1Qcml2YXRlS2V5RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Tb3VyY2VFbnZpcm9ubWVudCAkTURNU09VUkNFRU5WSVJPTk1FTlQgXAogIC1Tb3VyY2VSb2xlICRNRE1TT1VSQ0VST0xFIFwKICAtU291cmNlUm9sZUluc3RhbmNlICRNRE1TT1VSQ0VST0xFSU5TVEFOQ0UKRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgJU4KUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tZ2F0ZXdheSBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL2Fyby1nYXRld2F5IDw8RU9GCkFDUl9SRVNPVVJDRV9JRD0nJEFDUlJFU09VUkNFSUQnCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkFaVVJFX0RCVE9LRU5fQ0xJRU5UX0lEPSckREJUT0tFTkNMSUVOVElEJwpEQlRPS0VOX1VSTD0nJERCVE9LRU5VUkwnCk1ETV9BQ0NPVU5UPSIkUlBNRE1BQ0NPVU5UIgpNRE1fTkFNRVNQQUNFPUdhdGV3YXkKR0FURVdBWV9ET01BSU5TPSckR0FURVdBWURPTUFJTlMnCkdBVEVXQVlfRkVBVFVSRVM9JyRHQVRFV0FZRkVBVFVSRVMnClJQSU1BR0U9JyRSUElNQUdFJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tZ2F0ZXdheS5zZXJ2aWNlIDw8RU9GCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1nYXRld2F5CkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydFByZT0vdXNyL2Jpbi9ta2RpciAtcCAke2dhdGV3YXlfbG9nZGlyfQpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIEFDUl9SRVNPVVJDRV9JRCBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBBWlVSRV9EQlRPS0VOX0NMSUVOVF9JRCBcCiAgLWUgREJUT0tFTl9VUkwgXAogIC1lIEdBVEVXQVlfRE9NQUlOUyBcCiAgLWUgR0FURVdBWV9GRUFUVVJFUyBcCiAgLWUgTURNX0FDQ09VTlQgXAogIC1lIE1ETV9OQU1FU1BBQ0UgXAogIC1tIDJnIFwKICAtcCA4MDo4MDgwIFwKICAtcCA4MDgxOjgwODEgXAogIC1wIDQ0Mzo4NDQzIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgLXYgJHtnYXRld2F5X2xvZ2Rpcn06L2N0ci5sb2c6eiBcCiAgXCRSUElNQUdFIFwKICBnYXRld2F5CkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wIC10IDM2MDAgJU4KVGltZW91dFN0b3BTZWM9MzYwMApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmNoY29uIC1SIHN5c3RlbV91Om9iamVjdF9yOnZhcl9sb2dfdDpzMCAvdmFyL29wdC9taWNyb3NvZnQvbGludXhtb25hZ2VudAoKbWtkaXIgLXAgL3Zhci9saWIvd2FhZ2VudC9NaWNyb3NvZnQuQXp1cmUuS2V5VmF1bHQuU3RvcmUKCmVjaG8gImNvbmZpZ3VyaW5nIG1kc2QgYW5kIG1kbSBzZXJ2aWNlcyIKZm9yIHZhciBpbiAibWRzZCIgIm1kbSI7IGRvCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9kb3dubG9hZC0kdmFyLWNyZWRlbnRpYWxzLnNlcnZpY2UgPDxFT0YKW1VuaXRdCkRlc2NyaXB0aW9uPVBlcmlvZGljICR2YXIgY3JlZGVudGlhbHMgcmVmcmVzaAoKW1NlcnZpY2VdClR5cGU9b25lc2hvdApFeGVjU3RhcnQ9L3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggJHZhcgpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9kb3dubG9hZC0kdmFyLWNyZWRlbnRpYWxzLnRpbWVyIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1RpbWVyXQpPbkJvb3RTZWM9MG1pbgpPbkNhbGVuZGFyPTAvMTI6MDA6MDAKQWNjdXJhY3lTZWM9NXMKCltJbnN0YWxsXQpXYW50ZWRCeT10aW1lcnMudGFyZ2V0CkVPRgpkb25lCgpjYXQgPi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIDw8RU9GCiMhL2Jpbi9iYXNoCnNldCAtZXUKCkNPTVBPTkVOVD0iXCQxIgplY2hvICJEb3dubG9hZCBcJENPTVBPTkVOVCBjcmVkZW50aWFscyIKClRFTVBfRElSPVwkKG1rdGVtcCAtZCkKZXhwb3J0IEFaVVJFX0NPTkZJR19ESVI9XCQobWt0ZW1wIC1kKQoKZWNobyAiTG9nZ2luZyBpbnRvIEF6dXJlLi4uIgpSRVRSSUVTPTMKd2hpbGUgWyAiXCRSRVRSSUVTIiAtZ3QgMCBdOyBkbwogICAgaWYgYXogbG9naW4gLWkgLS1hbGxvdy1uby1zdWJzY3JpcHRpb25zCiAgICB0aGVuCiAgICAgICAgZWNobyAiYXogbG9naW4gc3VjY2Vzc2Z1bCIKICAgICAgICBicmVhawogICAgZWxzZQogICAgICAgIGVjaG8gImF6IGxvZ2luIGZhaWxlZC4gUmV0cnlpbmcuLi4iCiAgICAgICAgbGV0IFJFVFJJRVMtPTEKICAgICAgICBzbGVlcCA1CiAgICBmaQpkb25lCgp0cmFwICJjbGVhbnVwIiBFWElUCgpjbGVhbnVwKCkgewogIGF6IGxvZ291dAogIFtbICJcJFRFTVBfRElSIiA9fiAvdG1wLy4rIF1dICYmIHJtIC1yZiBcJFRFTVBfRElSCiAgW1sgIlwkQVpVUkVfQ09ORklHX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRBWlVSRV9DT05GSUdfRElSCn0KCmlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZG0iIF07IHRoZW4KICBDVVJSRU5UX0NFUlRfRklMRT0iL2V0Yy9tZG0ucGVtIgplbGlmIFsgIlwkQ09NUE9ORU5UIiA9ICJtZHNkIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii92YXIvbGliL3dhYWdlbnQvTWljcm9zb2Z0LkF6dXJlLktleVZhdWx0LlN0b3JlL21kc2QucGVtIgplbHNlCiAgZWNobyBJbnZhbGlkIHVzYWdlICYmIGV4aXQgMQpmaQoKU0VDUkVUX05BTUU9Imd3eS1cJHtDT01QT05FTlR9IgpORVdfQ0VSVF9GSUxFPSJcJFRFTVBfRElSL1wkQ09NUE9ORU5ULnBlbSIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIGF6IGtleXZhdWx0IHNlY3JldCBkb3dubG9hZCAtLWZpbGUgXCRORVdfQ0VSVF9GSUxFIC0taWQgImh0dHBzOi8vJEtFWVZBVUxUUFJFRklYLWd3eS4kS0VZVkFVTFRETlNTVUZGSVgvc2VjcmV0cy9cJFNFQ1JFVF9OQU1FIiAmJiBicmVhawogIGlmIFtbIFwkYXR0ZW1wdCAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmlmIFsgLWYgXCRORVdfQ0VSVF9GSUxFIF07IHRoZW4KICBpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogICAgY2hvd24gc3lzbG9nOnN5c2xvZyBcJE5FV19DRVJUX0ZJTEUKICBlbHNlCiAgICBzZWQgLWkgLW5lICcxLC9FTkQgQ0VSVElGSUNBVEUvIHAnIFwkTkVXX0NFUlRfRklMRQogIGZpCgogIG5ld19jZXJ0X3NuPSJcJChvcGVuc3NsIHg1MDkgLWluICJcJE5FV19DRVJUX0ZJTEUiIC1ub291dCAtc2VyaWFsIHwgYXdrIC1GPSAne3ByaW50IFwkMn0nKSIKICBjdXJyZW50X2NlcnRfc249IlwkKG9wZW5zc2wgeDUwOSAtaW4gIlwkQ1VSUkVOVF9DRVJUX0ZJTEUiIC1ub291dCAtc2VyaWFsIHwgYXdrIC1GPSAne3ByaW50IFwkMn0nKSIKICBpZiBbWyAhIC16IFwkbmV3X2NlcnRfc24gXV0gJiYgW1sgXCRuZXdfY2VydF9zbiAhPSAiXCRjdXJyZW50X2NlcnRfc24iIF1dOyB0aGVuCiAgICBlY2hvIHVwZGF0aW5nIGNlcnRpZmljYXRlIGZvciBcJENPTVBPTkVOVAogICAgY2htb2QgMDYwMCBcJE5FV19DRVJUX0ZJTEUKICAgIG12IFwkTkVXX0NFUlRfRklMRSBcJENVUlJFTlRfQ0VSVF9GSUxFCiAgZmkKZWxzZQogIGVjaG8gRmFpbGVkIHRvIHJlZnJlc2ggY2VydGlmaWNhdGUgZm9yIFwkQ09NUE9ORU5UICYmIGV4aXQgMQpmaQpFT0YKCmNobW9kIHUreCAvdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaAoKc3lzdGVtY3RsIGVuYWJsZSBkb3dubG9hZC1tZHNkLWNyZWRlbnRpYWxzLnRpbWVyCnN5c3RlbWN0bCBlbmFibGUgZG93bmxvYWQtbWRtLWNyZWRlbnRpYWxzLnRpbWVyCgovdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCBtZHNkCi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIG1kbQpNRFNEQ0VSVElGSUNBVEVTQU49JChvcGVuc3NsIHg1MDkgLWluIC92YXIvbGliL3dhYWdlbnQvTWljcm9zb2Z0LkF6dXJlLktleVZhdWx0LlN0b3JlL21kc2QucGVtIC1ub291dCAtc3ViamVjdCB8IHNlZCAtZSAncy8uKkNOID0gLy8nKQoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL3dhdGNoLW1kbS1jcmVkZW50aWFscy5zZXJ2aWNlIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1XYXRjaCBmb3IgY2hhbmdlcyBpbiBtZG0ucGVtIGFuZCByZXN0YXJ0cyB0aGUgbWRtIHNlcnZpY2UKCltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvYmluL3N5c3RlbWN0bCByZXN0YXJ0IG1kbS5zZXJ2aWNlCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnBhdGggPDxFT0YKW1BhdGhdClBhdGhNb2RpZmllZD0vZXRjL21kbS5wZW0KCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCnN5c3RlbWN0bCBlbmFibGUgd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnBhdGgKc3lzdGVtY3RsIHN0YXJ0IHdhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoCgpta2RpciAvZXRjL3N5c3RlbWQvc3lzdGVtL21kc2Quc2VydmljZS5kCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9tZHNkLnNlcnZpY2UuZC9vdmVycmlkZS5jb25mIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApFT0YKCmNhdCA+L2V0Yy9kZWZhdWx0L21kc2QgPDxFT0YKTURTRF9ST0xFX1BSRUZJWD0vdmFyL3J1bi9tZHNkL2RlZmF1bHQKTURTRF9PUFRJT05TPSItQSAtZCAtciBcJE1EU0RfUk9MRV9QUkVGSVgiCgpleHBvcnQgTU9OSVRPUklOR19HQ1NfRU5WSVJPTk1FTlQ9JyRNRFNERU5WSVJPTk1FTlQnCmV4cG9ydCBNT05JVE9SSU5HX0dDU19BQ0NPVU5UPSckUlBNRFNEQUNDT1VOVCcKZXhwb3J0IE1PTklUT1JJTkdfR0NTX1JFR0lPTj0nJExPQ0FUSU9OJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfQVVUSF9JRF9UWVBFPUF1dGhLZXlWYXVsdApleHBvcnQgTU9OSVRPUklOR19HQ1NfQVVUSF9JRD0nJE1EU0RDRVJUSUZJQ0FURVNBTicKZXhwb3J0IE1PTklUT1JJTkdfR0NTX05BTUVTUEFDRT0nJFJQTURTRE5BTUVTUEFDRScKZXhwb3J0IE1PTklUT1JJTkdfQ09ORklHX1ZFUlNJT049JyRHQVRFV0FZTURTRENPTkZJR1ZFUlNJT04nCmV4cG9ydCBNT05JVE9SSU5HX1VTRV9HRU5FVkFfQ09ORklHX1NFUlZJQ0U9dHJ1ZQoKZXhwb3J0IE1PTklUT1JJTkdfVEVOQU5UPSckTE9DQVRJT04nCmV4cG9ydCBNT05JVE9SSU5HX1JPTEU9Z2F0ZXdheQpleHBvcnQgTU9OSVRPUklOR19ST0xFX0lOU1RBTkNFPSckKGhvc3RuYW1lKScKCmV4cG9ydCBNRFNEX01TR1BBQ0tfU09SVF9DT0xVTU5TPTEKRU9GCgojIHNldHRpbmcgTU9OSVRPUklOR19HQ1NfQVVUSF9JRF9UWVBFPUF1dGhLZXlWYXVsdCBzZWVtcyB0byBoYXZlIGNhdXNlZCBtZHNkIG5vdAojIHRvIGhvbm91ciBTU0xfQ0VSVF9GSUxFIGFueSBtb3JlLCBoZWF2ZW4gb25seSBrbm93cyB3aHkuCm1rZGlyIC1wIC91c3IvbGliL3NzbC9jZXJ0cwpjc3BsaXQgLWYgL3Vzci9saWIvc3NsL2NlcnRzL2NlcnQtIC1iICUwM2QucGVtIC9ldGMvcGtpL3Rscy9jZXJ0cy9jYS1idW5kbGUuY3J0IC9eJC8xIHsqfSA+L2Rldi9udWxsCmNfcmVoYXNoIC91c3IvbGliL3NzbC9jZXJ0cwoKIyB3ZSBsZWF2ZSBjbGllbnRJZCBibGFuayBhcyBsb25nIGFzIG9ubHkgMSBtYW5hZ2VkIGlkZW50aXR5IGFzc2lnbmVkIHRvIHZtc3MKIyBpZiB3ZSBoYXZlIG1vcmUgdGhhbiAxLCB3ZSB3aWxsIG5lZWQgdG8gcG9wdWxhdGUgd2l0aCBjbGllbnRJZCB1c2VkIGZvciBvZmYtbm9kZSBzY2FubmluZwpjYXQgPi9ldGMvZGVmYXVsdC92c2Etbm9kZXNjYW4tYWdlbnQuY29uZmlnIDw8RU9GCnsKICAgICJOaWNlIjogMTksCiAgICAiVGltZW91dCI6IDEwODAwLAogICAgIkNsaWVudElkIjogIiIsCiAgICAiVGVuYW50SWQiOiAiJEFaVVJFU0VDUEFDS1ZTQVRFTkFOVElEIiwKICAgICJRdWFseXNTdG9yZUJhc2VVcmwiOiAiJEFaVVJFU0VDUEFDS1FVQUxZU1VSTCIsCiAgICAiUHJvY2Vzc1RpbWVvdXQiOiAzMDAsCiAgICAiQ29tbWFuZERlbGF5IjogMAogIH0KRU9GCgplY2hvICJlbmFibGluZyBhcm8gc2VydmljZXMiCmZvciBzZXJ2aWNlIGluIGFyby1nYXRld2F5IGF1b21zIGF6c2VjZCBhenNlY21vbmQgbWRzZCBtZG0gY2hyb255ZCBmbHVlbnRiaXQ7IGRvCiAgc3lzdGVtY3RsIGVuYWJsZSAkc2VydmljZS5zZXJ2aWNlCmRvbmUKCmZvciBzY2FuIGluIGJhc2VsaW5lIGNsYW1hdiBzb2Z0d2FyZTsgZG8KICAvdXNyL2xvY2FsL2Jpbi9henNlY2QgY29uZmlnIC1zICRzY2FuIC1kIFAxRApkb25lCgplY2hvICJyZWJvb3RpbmciCnJlc3RvcmVjb24gLVJGIC92YXIvbG9nLyoKKHNsZWVwIDMwOyByZWJvb3QpICYK')))]" } } } diff --git a/pkg/deploy/assets/rbac-development.json b/pkg/deploy/assets/rbac-development.json index 0d52a98b0..57f08d08e 100644 --- a/pkg/deploy/assets/rbac-development.json +++ b/pkg/deploy/assets/rbac-development.json @@ -26,7 +26,8 @@ { "actions": [ "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Resources/subscriptions/resourceGroups/write" + "Microsoft.Resources/subscriptions/resourceGroups/write", + "Microsoft.Authorization/*/action" ] } ], diff --git a/pkg/deploy/assets/rp-development.json b/pkg/deploy/assets/rp-development.json index 561d81a0f..b488666b9 100644 --- a/pkg/deploy/assets/rp-development.json +++ b/pkg/deploy/assets/rp-development.json @@ -54,32 +54,33 @@ "location": "[resourceGroup().location]" }, { + "apiVersion": "2023-04-15", "kind": "GlobalDocumentDB", + "location": "[resourceGroup().location]", + "name": "[parameters('databaseAccountName')]", "properties": { - "consistencyPolicy": { - "defaultConsistencyLevel": "Strong" - }, - "locations": [ - { - "locationName": "[resourceGroup().location]" - } - ], - "databaseAccountOfferType": "Standard", "backupPolicy": { "periodicModeProperties": { "backupIntervalInMinutes": 240, "backupRetentionIntervalInHours": 720 }, "type": "Periodic" - } + }, + "consistencyPolicy": { + "defaultConsistencyLevel": "Strong" + }, + "databaseAccountOfferType": "Standard", + "locations": [ + { + "locationName": "[resourceGroup().location]" + } + ], + "minimalTlsVersion": "Tls12" }, - "name": "[parameters('databaseAccountName')]", - "type": "Microsoft.DocumentDB/databaseAccounts", - "location": "[resourceGroup().location]", "tags": { "defaultExperience": "Core (SQL)" }, - "apiVersion": "2021-01-15" + "type": "Microsoft.DocumentDB/databaseAccounts" }, { "name": "[guid(resourceGroup().id, parameters('rpServicePrincipalId'), 'RP / Reader')]", diff --git a/pkg/deploy/assets/rp-production-parameters.json b/pkg/deploy/assets/rp-production-parameters.json index eb5855604..9e40f9398 100644 --- a/pkg/deploy/assets/rp-production-parameters.json +++ b/pkg/deploy/assets/rp-production-parameters.json @@ -29,12 +29,6 @@ "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 7b80cdb22..2fd7a5ade 100644 --- a/pkg/deploy/assets/rp-production.json +++ b/pkg/deploy/assets/rp-production.json @@ -45,14 +45,6 @@ "azureSecPackVSATenantId": { "type": "string" }, - "billingE2EStorageAccountId": { - "type": "string", - "defaultValue": "" - }, - "billingServicePrincipalId": { - "type": "string", - "defaultValue": "" - }, "clusterDefaultInstallerPullspec": { "type": "string", "defaultValue": "" @@ -431,7 +423,7 @@ }, "properties": { "upgradePolicy": { - "mode": "Manual" + "mode": "Rolling" }, "virtualMachineProfile": { "osProfile": { @@ -516,7 +508,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','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('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgojQWRkaW5nIHJldHJ5IGxvZ2ljIHRvIHl1bSBjb21tYW5kcyBpbiBvcmRlciB0byBhdm9pZCBzdGFsbGluZyBvdXQgb24gcmVzb3VyY2UgbG9ja3MKZWNobyAicnVubmluZyBSSFVJIGZpeCIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIHl1bSB1cGRhdGUgLXkgLS1kaXNhYmxlcmVwbz0nKicgLS1lbmFibGVyZXBvPSdyaHVpLW1pY3Jvc29mdC1henVyZSonICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gInJ1bm5pbmcgeXVtIHVwZGF0ZSIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIHl1bSAteSAteCBXQUxpbnV4QWdlbnQgLXggV0FMaW51eEFnZW50LXVkZXYgdXBkYXRlIC0tYWxsb3dlcmFzaW5nICYmIGJyZWFrCiAgaWYgW1sgJHthdHRlbXB0fSAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmVjaG8gImV4dGVuZGluZyBwYXJ0aXRpb24gdGFibGUiCiMgTGludXggYmxvY2sgZGV2aWNlcyBhcmUgaW5jb25zaXN0ZW50bHkgbmFtZWQKIyBpdCdzIGRpZmZpY3VsdCB0byB0aWUgdGhlIGx2bSBwdiB0byB0aGUgcGh5c2ljYWwgZGlzayB1c2luZyAvZGV2L2Rpc2sgZmlsZXMsIHdoaWNoIGlzIHdoeSBsdnMgaXMgdXNlZCBoZXJlCnBoeXNpY2FsRGlzaz0iJChsdnMgLW8gZGV2aWNlcyAtYSB8IGhlYWQgLW4yIHwgdGFpbCAtbjEgfCBjdXQgLWQgJyAnIC1mIDMgfCBjdXQgLWQgXCggLWYgMSB8IHRyIC1kICdbOmRpZ2l0Ol0nKSIKZ3Jvd3BhcnQgIiRwaHlzaWNhbERpc2siIDIKCmVjaG8gImV4dGVuZGluZyBmaWxlc3lzdGVtcyIKbHZleHRlbmQgLWwgKzIwJUZSRUUgL2Rldi9yb290dmcvcm9vdGx2Cnhmc19ncm93ZnMgLwoKbHZleHRlbmQgLWwgKzEwMCVGUkVFIC9kZXYvcm9vdHZnL3Zhcmx2Cnhmc19ncm93ZnMgL3ZhcgoKZWNobyAiaW1wb3J0aW5nIHJwbSByZXBvc2l0b3JpZXMiCnJwbSAtLWltcG9ydCBodHRwczovL2RsLmZlZG9yYXByb2plY3Qub3JnL3B1Yi9lcGVsL1JQTS1HUEctS0VZLUVQRUwtOApycG0gLS1pbXBvcnQgaHR0cHM6Ly9wYWNrYWdlcy5taWNyb3NvZnQuY29tL2tleXMvbWljcm9zb2Z0LmFzYwoKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIHl1bSAteSBpbnN0YWxsIGh0dHBzOi8vZGwuZmVkb3JhcHJvamVjdC5vcmcvcHViL2VwZWwvZXBlbC1yZWxlYXNlLWxhdGVzdC04Lm5vYXJjaC5ycG0gJiYgYnJlYWsKICBpZiBbWyAke2F0dGVtcHR9IC1sdCA1IF1dOyB0aGVuIHNsZWVwIDEwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKZWNobyAiY29uZmlndXJpbmcgbG9ncm90YXRlIgpjYXQgPi9ldGMvbG9ncm90YXRlLmNvbmYgPDwnRU9GJwojIHNlZSAibWFuIGxvZ3JvdGF0ZSIgZm9yIGRldGFpbHMKIyByb3RhdGUgbG9nIGZpbGVzIHdlZWtseQp3ZWVrbHkKCiMga2VlcCAyIHdlZWtzIHdvcnRoIG9mIGJhY2tsb2dzCnJvdGF0ZSAyCgojIGNyZWF0ZSBuZXcgKGVtcHR5KSBsb2cgZmlsZXMgYWZ0ZXIgcm90YXRpbmcgb2xkIG9uZXMKY3JlYXRlCgojIHVzZSBkYXRlIGFzIGEgc3VmZml4IG9mIHRoZSByb3RhdGVkIGZpbGUKZGF0ZWV4dAoKIyB1bmNvbW1lbnQgdGhpcyBpZiB5b3Ugd2FudCB5b3VyIGxvZyBmaWxlcyBjb21wcmVzc2VkCmNvbXByZXNzCgojIFJQTSBwYWNrYWdlcyBkcm9wIGxvZyByb3RhdGlvbiBpbmZvcm1hdGlvbiBpbnRvIHRoaXMgZGlyZWN0b3J5CmluY2x1ZGUgL2V0Yy9sb2dyb3RhdGUuZAoKIyBubyBwYWNrYWdlcyBvd24gd3RtcCBhbmQgYnRtcCAtLSB3ZSdsbCByb3RhdGUgdGhlbSBoZXJlCi92YXIvbG9nL3d0bXAgewogICAgbW9udGhseQogICAgY3JlYXRlIDA2NjQgcm9vdCB1dG1wCiAgICAgICAgbWluc2l6ZSAxTQogICAgcm90YXRlIDEKfQoKL3Zhci9sb2cvYnRtcCB7CiAgICBtaXNzaW5nb2sKICAgIG1vbnRobHkKICAgIGNyZWF0ZSAwNjAwIHJvb3QgdXRtcAogICAgcm90YXRlIDEKfQpFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIHl1bSByZXBvc2l0b3J5IGFuZCBydW5uaW5nIHl1bSB1cGRhdGUiCmNhdCA+L2V0Yy95dW0ucmVwb3MuZC9henVyZS5yZXBvIDw8J0VPRicKW2F6dXJlLWNsaV0KbmFtZT1henVyZS1jbGkKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmUtY2xpCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPXllcwoKW2F6dXJlY29yZV0KbmFtZT1henVyZWNvcmUKYmFzZXVybD1odHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20veXVtcmVwb3MvYXp1cmVjb3JlCmVuYWJsZWQ9eWVzCmdwZ2NoZWNrPW5vCkVPRgoKc2VtYW5hZ2UgZmNvbnRleHQgLWEgLXQgdmFyX2xvZ190ICIvdmFyL2xvZy9qb3VybmFsKC8uKik/Igpta2RpciAtcCAvdmFyL2xvZy9qb3VybmFsCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNX07IGRvCnl1bSAteSBpbnN0YWxsIGNsYW1hdiBhenNlYy1jbGFtYXYgYXpzZWMtbW9uaXRvciBhenVyZS1jbGkgYXp1cmUtbWRzZCBhenVyZS1zZWN1cml0eSBwb2RtYW4gcG9kbWFuLWRvY2tlciBvcGVuc3NsLXBlcmwgcHl0aG9uMyAmJiBicmVhawogICMgaGFjayAtIHdlIGFyZSBpbnN0YWxsaW5nIHB5dGhvbjMgb24gaG9zdHMgZHVlIHRvIGFuIGlzc3VlIHdpdGggQXp1cmUgTGludXggRXh0ZW5zaW9ucyBodHRwczovL2dpdGh1Yi5jb20vQXp1cmUvYXp1cmUtbGludXgtZXh0ZW5zaW9ucy9wdWxsLzE1MDUKICBpZiBbWyAke2F0dGVtcHR9IC1sdCA1IF1dOyB0aGVuIHNsZWVwIDEwOyBlbHNlIGV4aXQgMTsgZmkKZG9uZQoKIyBodHRwczovL2FjY2Vzcy5yZWRoYXQuY29tL3NlY3VyaXR5L2N2ZS9jdmUtMjAyMC0xMzQwMQplY2hvICJhcHBseWluZyBmaXJld2FsbCBydWxlcyIKY2F0ID4vZXRjL3N5c2N0bC5kLzAyLWRpc2FibGUtYWNjZXB0LXJhLmNvbmYgPDwnRU9GJwpuZXQuaXB2Ni5jb25mLmFsbC5hY2NlcHRfcmE9MApFT0YKCmNhdCA+L2V0Yy9zeXNjdGwuZC8wMS1kaXNhYmxlLWNvcmUuY29uZiA8PCdFT0YnCmtlcm5lbC5jb3JlX3BhdHRlcm4gPSB8L2Jpbi90cnVlCkVPRgpzeXNjdGwgLS1zeXN0ZW0KCmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0My90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9NDQ0L3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDUvdGNwIC0tcGVybWFuZW50CmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTIyMjIvdGNwIC0tcGVybWFuZW50CgpleHBvcnQgQVpVUkVfQ0xPVURfTkFNRT0kQVpVUkVDTE9VRE5BTUUKCmVjaG8gImxvZ2dpbmcgaW50byBwcm9kIGFjciIKYXogbG9naW4gLWkgLS1hbGxvdy1uby1zdWJzY3JpcHRpb25zCgojIFN1cHByZXNzIGVtdWxhdGlvbiBvdXRwdXQgZm9yIHBvZG1hbiBpbnN0ZWFkIG9mIGRvY2tlciBmb3IgYXogYWNyIGNvbXBhdGFiaWxpdHkKbWtkaXIgLXAgL2V0Yy9jb250YWluZXJzLwp0b3VjaCAvZXRjL2NvbnRhaW5lcnMvbm9kb2NrZXIKCm1rZGlyIC1wIC9yb290Ly5kb2NrZXIKUkVHSVNUUllfQVVUSF9GSUxFPS9yb290Ly5kb2NrZXIvY29uZmlnLmpzb24gYXogYWNyIGxvZ2luIC0tbmFtZSAiJChzZWQgLWUgJ3N8LiovfHwnIDw8PCIkQUNSUkVTT1VSQ0VJRCIpIgoKTURNSU1BR0U9IiR7UlBJTUFHRSUlLyp9LyR7TURNSU1BR0UjIyovfSIKZG9ja2VyIHB1bGwgIiRNRE1JTUFHRSIKZG9ja2VyIHB1bGwgIiRSUElNQUdFIgpkb2NrZXIgcHVsbCAiJEZMVUVOVEJJVElNQUdFIgoKYXogbG9nb3V0CgplY2hvICJjb25maWd1cmluZyBmbHVlbnRiaXQgc2VydmljZSIKbWtkaXIgLXAgL2V0Yy9mbHVlbnRiaXQvCm1rZGlyIC1wIC92YXIvbGliL2ZsdWVudAoKY2F0ID4vZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZiA8PCdFT0YnCltJTlBVVF0KCU5hbWUgc3lzdGVtZAoJVGFnIGpvdXJuYWxkCglTeXN0ZW1kX0ZpbHRlciBfQ09NTT1hcm8KCURCIC92YXIvbGliL2ZsdWVudC9qb3VybmFsZGIKCltGSUxURVJdCglOYW1lIG1vZGlmeQoJTWF0Y2ggam91cm5hbGQKCVJlbW92ZV93aWxkY2FyZCBfCglSZW1vdmUgVElNRVNUQU1QCgpbRklMVEVSXQoJTmFtZSByZXdyaXRlX3RhZwoJTWF0Y2ggam91cm5hbGQKCVJ1bGUgJExPR0tJTkQgYXN5bmNxb3MgYXN5bmNxb3MgdHJ1ZQoKW0ZJTFRFUl0KCU5hbWUgbW9kaWZ5CglNYXRjaCBhc3luY3FvcwoJUmVtb3ZlIENMSUVOVF9QUklOQ0lQQUxfTkFNRQoJUmVtb3ZlIEZJTEUKCVJlbW92ZSBDT01QT05FTlQKCltGSUxURVJdCglOYW1lIHJld3JpdGVfdGFnCglNYXRjaCBqb3VybmFsZAoJUnVsZSAkTE9HS0lORCBpZnhhdWRpdCBpZnhhdWRpdCBmYWxzZQoKW09VVFBVVF0KCU5hbWUgZm9yd2FyZAoJTWF0Y2ggKgoJUG9ydCAyOTIzMApFT0YKCmVjaG8gIkZMVUVOVEJJVElNQUdFPSRGTFVFTlRCSVRJTUFHRSIgPi9ldGMvc3lzY29uZmlnL2ZsdWVudGJpdAoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2ZsdWVudGJpdC5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKU3RhcnRMaW1pdEludGVydmFsU2VjPTAKCltTZXJ2aWNlXQpSZXN0YXJ0U2VjPTFzCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9mbHVlbnRiaXQKRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tc2VjdXJpdHktb3B0IGxhYmVsPWRpc2FibGUgXAogIC0tZW50cnlwb2ludCAvb3B0L3RkLWFnZW50LWJpdC9iaW4vdGQtYWdlbnQtYml0IFwKICAtLW5ldD1ob3N0IFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLXYgL2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmY6L2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYgXAogIC12IC92YXIvbGliL2ZsdWVudDovdmFyL2xpYi9mbHVlbnQ6eiBcCiAgLXYgL3Zhci9sb2cvam91cm5hbDovdmFyL2xvZy9qb3VybmFsOnJvIFwKICAtdiAvZXRjL21hY2hpbmUtaWQ6L2V0Yy9tYWNoaW5lLWlkOnJvIFwKICAkRkxVRU5UQklUSU1BR0UgXAogIC1jIC9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mCgpFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAlTgpSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTUKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCm1rZGlyIC9ldGMvYXJvLXJwCmJhc2U2NCAtZCA8PDwiJEFETUlOQVBJQ0FCVU5ETEUiID4vZXRjL2Fyby1ycC9hZG1pbi1jYS1idW5kbGUucGVtCmlmIFtbIC1uICIkQVJNQVBJQ0FCVU5ETEUiIF1dOyB0aGVuCiAgYmFzZTY0IC1kIDw8PCIkQVJNQVBJQ0FCVU5ETEUiID4vZXRjL2Fyby1ycC9hcm0tY2EtYnVuZGxlLnBlbQpmaQpjaG93biAtUiAxMDAwOjEwMDAgL2V0Yy9hcm8tcnAKCmVjaG8gImNvbmZpZ3VyaW5nIG1kbSBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL21kbSA8PEVPRgpNRE1GUk9OVEVORFVSTD0nJE1ETUZST05URU5EVVJMJwpNRE1JTUFHRT0nJE1ETUlNQUdFJwpNRE1TT1VSQ0VFTlZJUk9OTUVOVD0nJExPQ0FUSU9OJwpNRE1TT1VSQ0VST0xFPXJwCk1ETVNPVVJDRVJPTEVJTlNUQU5DRT0nJChob3N0bmFtZSknCkVPRgoKbWtkaXIgL3Zhci9ldHcKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL21kbS5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvbWRtCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWVudHJ5cG9pbnQgL3Vzci9zYmluL01ldHJpY3NFeHRlbnNpb24gXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtbSAyZyBcCiAgLXYgL2V0Yy9tZG0ucGVtOi9ldGMvbWRtLnBlbSBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJE1ETUlNQUdFIFwKICAtQ2VydEZpbGUgL2V0Yy9tZG0ucGVtIFwKICAtRnJvbnRFbmRVcmwgJE1ETUZST05URU5EVVJMIFwKICAtTG9nZ2VyIENvbnNvbGUgXAogIC1Mb2dMZXZlbCBXYXJuaW5nIFwKICAtUHJpdmF0ZUtleUZpbGUgL2V0Yy9tZG0ucGVtIFwKICAtU291cmNlRW52aXJvbm1lbnQgJE1ETVNPVVJDRUVOVklST05NRU5UIFwKICAtU291cmNlUm9sZSAkTURNU09VUkNFUk9MRSBcCiAgLVNvdXJjZVJvbGVJbnN0YW5jZSAkTURNU09VUkNFUk9MRUlOU1RBTkNFCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVOClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKZWNobyAiY29uZmlndXJpbmcgYXJvLXJwIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLXJwIDw8RU9GCkFDUl9SRVNPVVJDRV9JRD0nJEFDUlJFU09VUkNFSUQnCkFETUlOX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRT0nJEFETUlOQVBJQ0xJRU5UQ0VSVENPTU1PTk5BTUUnCkFSTV9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUU9JyRBUk1BUElDTElFTlRDRVJUQ09NTU9OTkFNRScKQVpVUkVfQVJNX0NMSUVOVF9JRD0nJEFSTUNMSUVOVElEJwpBWlVSRV9GUF9DTElFTlRfSUQ9JyRGUENMSUVOVElEJwpBWlVSRV9GUF9TRVJWSUNFX1BSSU5DSVBBTF9JRD0nJEZQU0VSVklDRVBSSU5DSVBBTElEJwpCSUxMSU5HX0UyRV9TVE9SQUdFX0FDQ09VTlRfSUQ9JyRCSUxMSU5HRTJFU1RPUkFHRUFDQ09VTlRJRCcKQ0xVU1RFUl9NRE1fQUNDT1VOVD0nJENMVVNURVJNRE1BQ0NPVU5UJwpDTFVTVEVSX01ETV9OQU1FU1BBQ0U9UlAKQ0xVU1RFUl9NRFNEX0FDQ09VTlQ9JyRDTFVTVEVSTURTREFDQ09VTlQnCkNMVVNURVJfTURTRF9DT05GSUdfVkVSU0lPTj0nJENMVVNURVJNRFNEQ09ORklHVkVSU0lPTicKQ0xVU1RFUl9NRFNEX05BTUVTUEFDRT0nJENMVVNURVJNRFNETkFNRVNQQUNFJwpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpET01BSU5fTkFNRT0nJExPQ0FUSU9OLiRDTFVTVEVSUEFSRU5URE9NQUlOTkFNRScKR0FURVdBWV9ET01BSU5TPSckR0FURVdBWURPTUFJTlMnCkdBVEVXQVlfUkVTT1VSQ0VHUk9VUD0nJEdBVEVXQVlSRVNPVVJDRUdST1VQTkFNRScKS0VZVkFVTFRfUFJFRklYPSckS0VZVkFVTFRQUkVGSVgnCk1ETV9BQ0NPVU5UPSckUlBNRE1BQ0NPVU5UJwpNRE1fTkFNRVNQQUNFPVJQCk1EU0RfRU5WSVJPTk1FTlQ9JyRNRFNERU5WSVJPTk1FTlQnClJQX0ZFQVRVUkVTPSckUlBGRUFUVVJFUycKUlBJTUFHRT0nJFJQSU1BR0UnCkFST19JTlNUQUxMX1ZJQV9ISVZFPSckQ0xVU1RFUlNJTlNUQUxMVklBSElWRScKQVJPX0hJVkVfREVGQVVMVF9JTlNUQUxMRVJfUFVMTFNQRUM9JyRDTFVTVEVSREVGQVVMVElOU1RBTExFUlBVTExTUEVDJwpBUk9fQURPUFRfQllfSElWRT0nJENMVVNURVJTQURPUFRCWUhJVkUnClVTRV9DSEVDS0FDQ0VTUz0nJFVTRUNIRUNLQUNDRVNTJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tcnAuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1ycApFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIEFDUl9SRVNPVVJDRV9JRCBcCiAgLWUgQURNSU5fQVBJX0NMSUVOVF9DRVJUX0NPTU1PTl9OQU1FIFwKICAtZSBBUk1fQVBJX0NMSUVOVF9DRVJUX0NPTU1PTl9OQU1FIFwKICAtZSBBWlVSRV9BUk1fQ0xJRU5UX0lEIFwKICAtZSBBWlVSRV9GUF9DTElFTlRfSUQgXAogIC1lIEJJTExJTkdfRTJFX1NUT1JBR0VfQUNDT1VOVF9JRCBcCiAgLWUgQ0xVU1RFUl9NRE1fQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRE1fTkFNRVNQQUNFIFwKICAtZSBDTFVTVEVSX01EU0RfQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OIFwKICAtZSBDTFVTVEVSX01EU0RfTkFNRVNQQUNFIFwKICAtZSBEQVRBQkFTRV9BQ0NPVU5UX05BTUUgXAogIC1lIERPTUFJTl9OQU1FIFwKICAtZSBHQVRFV0FZX0RPTUFJTlMgXAogIC1lIEdBVEVXQVlfUkVTT1VSQ0VHUk9VUCBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLWUgTURTRF9FTlZJUk9OTUVOVCBcCiAgLWUgUlBfRkVBVFVSRVMgXAogIC1lIEFST19JTlNUQUxMX1ZJQV9ISVZFIFwKICAtZSBBUk9fSElWRV9ERUZBVUxUX0lOU1RBTExFUl9QVUxMU1BFQyBcCiAgLWUgQVJPX0FET1BUX0JZX0hJVkUgXAogIC1lIFVTRV9DSEVDS0FDQ0VTUyBcCiAgLW0gMmcgXAogIC1wIDQ0Mzo4NDQzIFwKICAtdiAvZXRjL2Fyby1ycDovZXRjL2Fyby1ycCBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBycApFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAtdCAzNjAwICVOClRpbWVvdXRTdG9wU2VjPTM2MDAKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tZGJ0b2tlbiBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL2Fyby1kYnRva2VuIDw8RU9GCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkFaVVJFX0RCVE9LRU5fQ0xJRU5UX0lEPSckREJUT0tFTkNMSUVOVElEJwpBWlVSRV9HQVRFV0FZX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckR0FURVdBWVNFUlZJQ0VQUklOQ0lQQUxJRCcKS0VZVkFVTFRfUFJFRklYPSckS0VZVkFVTFRQUkVGSVgnCk1ETV9BQ0NPVU5UPSckUlBNRE1BQ0NPVU5UJwpNRE1fTkFNRVNQQUNFPURCVG9rZW4KUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1kYnRva2VuLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tZGJ0b2tlbgpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIEFaVVJFX0dBVEVXQVlfU0VSVklDRV9QUklOQ0lQQUxfSUQgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgQVpVUkVfREJUT0tFTl9DTElFTlRfSUQgXAogIC1lIEtFWVZBVUxUX1BSRUZJWCBcCiAgLWUgTURNX0FDQ09VTlQgXAogIC1lIE1ETV9OQU1FU1BBQ0UgXAogIC1tIDJnIFwKICAtcCA0NDU6ODQ0NSBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBkYnRva2VuCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wIC10IDM2MDAgJU4KVGltZW91dFN0b3BTZWM9MzYwMApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCiMgRE9NQUlOX05BTUUsIENMVVNURVJfTURTRF9BQ0NPVU5ULCBDTFVTVEVSX01EU0RfQ09ORklHX1ZFUlNJT04sIEdBVEVXQVlfRE9NQUlOUywgR0FURVdBWV9SRVNPVVJDRUdST1VQLCBNRFNEX0VOVklST05NRU5UIENMVVNURVJfTURTRF9OQU1FU1BBQ0UKIyBhcmUgbm90IHVzZWQsIGJ1dCBjYW4ndCBlYXNpbHkgYmUgcmVmYWN0b3JlZCBvdXQuIFNob3VsZCBiZSByZXZpc2l0ZWQgaW4gdGhlIGZ1dHVyZS4KZWNobyAiY29uZmlndXJpbmcgYXJvLW1vbml0b3Igc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tbW9uaXRvciA8PEVPRgpBWlVSRV9GUF9DTElFTlRfSUQ9JyRGUENMSUVOVElEJwpET01BSU5fTkFNRT0nJExPQ0FUSU9OLiRDTFVTVEVSUEFSRU5URE9NQUlOTkFNRScKQ0xVU1RFUl9NRFNEX0FDQ09VTlQ9JyRDTFVTVEVSTURTREFDQ09VTlQnCkNMVVNURVJfTURTRF9DT05GSUdfVkVSU0lPTj0nJENMVVNURVJNRFNEQ09ORklHVkVSU0lPTicKR0FURVdBWV9ET01BSU5TPSckR0FURVdBWURPTUFJTlMnCkdBVEVXQVlfUkVTT1VSQ0VHUk9VUD0nJEdBVEVXQVlSRVNPVVJDRUdST1VQTkFNRScKTURTRF9FTlZJUk9OTUVOVD0nJE1EU0RFTlZJUk9OTUVOVCcKQ0xVU1RFUl9NRFNEX05BTUVTUEFDRT0nJENMVVNURVJNRFNETkFNRVNQQUNFJwpDTFVTVEVSX01ETV9BQ0NPVU5UPSckQ0xVU1RFUk1ETUFDQ09VTlQnCkNMVVNURVJfTURNX05BTUVTUEFDRT1CQk0KREFUQUJBU0VfQUNDT1VOVF9OQU1FPSckREFUQUJBU0VBQ0NPVU5UTkFNRScKS0VZVkFVTFRfUFJFRklYPSckS0VZVkFVTFRQUkVGSVgnCk1ETV9BQ0NPVU5UPSckUlBNRE1BQ0NPVU5UJwpNRE1fTkFNRVNQQUNFPUJCTQpSUElNQUdFPSckUlBJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vYXJvLW1vbml0b3Iuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKV2FudHM9bmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2Fyby1tb25pdG9yCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQVpVUkVfRlBfQ0xJRU5UX0lEIFwKICAtZSBET01BSU5fTkFNRSBcCiAgLWUgQ0xVU1RFUl9NRFNEX0FDQ09VTlQgXAogIC1lIENMVVNURVJfTURTRF9DT05GSUdfVkVSU0lPTiBcCiAgLWUgR0FURVdBWV9ET01BSU5TIFwKICAtZSBHQVRFV0FZX1JFU09VUkNFR1JPVVAgXAogIC1lIE1EU0RfRU5WSVJPTk1FTlQgXAogIC1lIENMVVNURVJfTURTRF9OQU1FU1BBQ0UgXAogIC1lIENMVVNURVJfTURNX0FDQ09VTlQgXAogIC1lIENMVVNURVJfTURNX05BTUVTUEFDRSBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtbSAyLjVnIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIG1vbml0b3IKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcG9ydGFsIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLXBvcnRhbCA8PEVPRgpBWlVSRV9QT1JUQUxfQUNDRVNTX0dST1VQX0lEUz0nJFBPUlRBTEFDQ0VTU0dST1VQSURTJwpBWlVSRV9QT1JUQUxfQ0xJRU5UX0lEPSckUE9SVEFMQ0xJRU5USUQnCkFaVVJFX1BPUlRBTF9FTEVWQVRFRF9HUk9VUF9JRFM9JyRQT1JUQUxFTEVWQVRFREdST1VQSURTJwpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9UG9ydGFsClBPUlRBTF9IT1NUTkFNRT0nJExPQ0FUSU9OLmFkbWluLiRSUFBBUkVOVERPTUFJTk5BTUUnClJQSU1BR0U9JyRSUElNQUdFJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tcG9ydGFsLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWw9MAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tcG9ydGFsCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQVpVUkVfUE9SVEFMX0FDQ0VTU19HUk9VUF9JRFMgXAogIC1lIEFaVVJFX1BPUlRBTF9DTElFTlRfSUQgXAogIC1lIEFaVVJFX1BPUlRBTF9FTEVWQVRFRF9HUk9VUF9JRFMgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLWUgUE9SVEFMX0hPU1ROQU1FIFwKICAtbSAyZyBcCiAgLXAgNDQ0Ojg0NDQgXAogIC1wIDIyMjI6MjIyMiBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBwb3J0YWwKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBtZHNkIGFuZCBtZG0gc2VydmljZXMiCmNoY29uIC1SIHN5c3RlbV91Om9iamVjdF9yOnZhcl9sb2dfdDpzMCAvdmFyL29wdC9taWNyb3NvZnQvbGludXhtb25hZ2VudAoKbWtkaXIgLXAgL3Zhci9saWIvd2FhZ2VudC9NaWNyb3NvZnQuQXp1cmUuS2V5VmF1bHQuU3RvcmUKCmZvciB2YXIgaW4gIm1kc2QiICJtZG0iOyBkbwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy5zZXJ2aWNlIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKCltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoICR2YXIKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy50aW1lciA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249UGVyaW9kaWMgJHZhciBjcmVkZW50aWFscyByZWZyZXNoCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltUaW1lcl0KT25Cb290U2VjPTBtaW4KT25DYWxlbmRhcj0wLzEyOjAwOjAwCkFjY3VyYWN5U2VjPTVzCgpbSW5zdGFsbF0KV2FudGVkQnk9dGltZXJzLnRhcmdldApFT0YKZG9uZQoKY2F0ID4vdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCA8PEVPRgojIS9iaW4vYmFzaApzZXQgLWV1CgpDT01QT05FTlQ9IlwkMSIKZWNobyAiRG93bmxvYWQgXCRDT01QT05FTlQgY3JlZGVudGlhbHMiCgpURU1QX0RJUj1cJChta3RlbXAgLWQpCmV4cG9ydCBBWlVSRV9DT05GSUdfRElSPVwkKG1rdGVtcCAtZCkKCmVjaG8gIkxvZ2dpbmcgaW50byBBenVyZS4uLiIKUkVUUklFUz0zCndoaWxlIFsgIlwkUkVUUklFUyIgLWd0IDAgXTsgZG8KICAgIGlmIGF6IGxvZ2luIC1pIC0tYWxsb3ctbm8tc3Vic2NyaXB0aW9ucwogICAgdGhlbgogICAgICAgIGVjaG8gImF6IGxvZ2luIHN1Y2Nlc3NmdWwiCiAgICAgICAgYnJlYWsKICAgIGVsc2UKICAgICAgICBlY2hvICJheiBsb2dpbiBmYWlsZWQuIFJldHJ5aW5nLi4uIgogICAgICAgIGxldCBSRVRSSUVTLT0xCiAgICAgICAgc2xlZXAgNQogICAgZmkKZG9uZQoKdHJhcCAiY2xlYW51cCIgRVhJVAoKY2xlYW51cCgpIHsKICBheiBsb2dvdXQKICBbWyAiXCRURU1QX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRURU1QX0RJUgogIFtbICJcJEFaVVJFX0NPTkZJR19ESVIiID1+IC90bXAvLisgXV0gJiYgcm0gLXJmIFwkQVpVUkVfQ09ORklHX0RJUgp9CgppZiBbICJcJENPTVBPTkVOVCIgPSAibWRtIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii9ldGMvbWRtLnBlbSIKZWxpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogIENVUlJFTlRfQ0VSVF9GSUxFPSIvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSIKZWxzZQogIGVjaG8gSW52YWxpZCB1c2FnZSAmJiBleGl0IDEKZmkKClNFQ1JFVF9OQU1FPSJycC1cJHtDT01QT05FTlR9IgpORVdfQ0VSVF9GSUxFPSJcJFRFTVBfRElSL1wkQ09NUE9ORU5ULnBlbSIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIGF6IGtleXZhdWx0IHNlY3JldCBkb3dubG9hZCAtLWZpbGUgXCRORVdfQ0VSVF9GSUxFIC0taWQgImh0dHBzOi8vJEtFWVZBVUxUUFJFRklYLXN2Yy4kS0VZVkFVTFRETlNTVUZGSVgvc2VjcmV0cy9cJFNFQ1JFVF9OQU1FIiAmJiBicmVhawogIGlmIFtbIFwkYXR0ZW1wdCAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmlmIFsgLWYgXCRORVdfQ0VSVF9GSUxFIF07IHRoZW4KICBpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogICAgY2hvd24gc3lzbG9nOnN5c2xvZyBcJE5FV19DRVJUX0ZJTEUKICBlbHNlCiAgICBzZWQgLWkgLW5lICcxLC9FTkQgQ0VSVElGSUNBVEUvIHAnIFwkTkVXX0NFUlRfRklMRQogIGZpCgogIG5ld19jZXJ0X3NuPSJcJChvcGVuc3NsIHg1MDkgLWluICJcJE5FV19DRVJUX0ZJTEUiIC1ub291dCAtc2VyaWFsIHwgYXdrIC1GPSAne3ByaW50IFwkMn0nKSIKICBjdXJyZW50X2NlcnRfc249IlwkKG9wZW5zc2wgeDUwOSAtaW4gIlwkQ1VSUkVOVF9DRVJUX0ZJTEUiIC1ub291dCAtc2VyaWFsIHwgYXdrIC1GPSAne3ByaW50IFwkMn0nKSIKICBpZiBbWyAhIC16IFwkbmV3X2NlcnRfc24gXV0gJiYgW1sgXCRuZXdfY2VydF9zbiAhPSAiXCRjdXJyZW50X2NlcnRfc24iIF1dOyB0aGVuCiAgICBlY2hvIHVwZGF0aW5nIGNlcnRpZmljYXRlIGZvciBcJENPTVBPTkVOVAogICAgY2htb2QgMDYwMCBcJE5FV19DRVJUX0ZJTEUKICAgIG12IFwkTkVXX0NFUlRfRklMRSBcJENVUlJFTlRfQ0VSVF9GSUxFCiAgZmkKZWxzZQogIGVjaG8gRmFpbGVkIHRvIHJlZnJlc2ggY2VydGlmaWNhdGUgZm9yIFwkQ09NUE9ORU5UICYmIGV4aXQgMQpmaQpFT0YKCmNobW9kIHUreCAvdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaAoKc3lzdGVtY3RsIGVuYWJsZSBkb3dubG9hZC1tZHNkLWNyZWRlbnRpYWxzLnRpbWVyCnN5c3RlbWN0bCBlbmFibGUgZG93bmxvYWQtbWRtLWNyZWRlbnRpYWxzLnRpbWVyCgovdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCBtZHNkCi91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoIG1kbQpNRFNEQ0VSVElGSUNBVEVTQU49JChvcGVuc3NsIHg1MDkgLWluIC92YXIvbGliL3dhYWdlbnQvTWljcm9zb2Z0LkF6dXJlLktleVZhdWx0LlN0b3JlL21kc2QucGVtIC1ub291dCAtc3ViamVjdCB8IHNlZCAtZSAncy8uKkNOID0gLy8nKQoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL3dhdGNoLW1kbS1jcmVkZW50aWFscy5zZXJ2aWNlIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1XYXRjaCBmb3IgY2hhbmdlcyBpbiBtZG0ucGVtIGFuZCByZXN0YXJ0cyB0aGUgbWRtIHNlcnZpY2UKCltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvYmluL3N5c3RlbWN0bCByZXN0YXJ0IG1kbS5zZXJ2aWNlCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnBhdGggPDxFT0YKW1BhdGhdClBhdGhNb2RpZmllZD0vZXRjL21kbS5wZW0KCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCnN5c3RlbWN0bCBlbmFibGUgd2F0Y2gtbWRtLWNyZWRlbnRpYWxzLnBhdGgKc3lzdGVtY3RsIHN0YXJ0IHdhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoCgpta2RpciAvZXRjL3N5c3RlbWQvc3lzdGVtL21kc2Quc2VydmljZS5kCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9tZHNkLnNlcnZpY2UuZC9vdmVycmlkZS5jb25mIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApFT0YKCmNhdCA+L2V0Yy9kZWZhdWx0L21kc2QgPDxFT0YKTURTRF9ST0xFX1BSRUZJWD0vdmFyL3J1bi9tZHNkL2RlZmF1bHQKTURTRF9PUFRJT05TPSItQSAtZCAtciBcJE1EU0RfUk9MRV9QUkVGSVgiCgpleHBvcnQgTU9OSVRPUklOR19HQ1NfRU5WSVJPTk1FTlQ9JyRNRFNERU5WSVJPTk1FTlQnCmV4cG9ydCBNT05JVE9SSU5HX0dDU19BQ0NPVU5UPSckUlBNRFNEQUNDT1VOVCcKZXhwb3J0IE1PTklUT1JJTkdfR0NTX1JFR0lPTj0nJExPQ0FUSU9OJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfQVVUSF9JRF9UWVBFPUF1dGhLZXlWYXVsdApleHBvcnQgTU9OSVRPUklOR19HQ1NfQVVUSF9JRD0nJE1EU0RDRVJUSUZJQ0FURVNBTicKZXhwb3J0IE1PTklUT1JJTkdfR0NTX05BTUVTUEFDRT0nJFJQTURTRE5BTUVTUEFDRScKZXhwb3J0IE1PTklUT1JJTkdfQ09ORklHX1ZFUlNJT049JyRSUE1EU0RDT05GSUdWRVJTSU9OJwpleHBvcnQgTU9OSVRPUklOR19VU0VfR0VORVZBX0NPTkZJR19TRVJWSUNFPXRydWUKCmV4cG9ydCBNT05JVE9SSU5HX1RFTkFOVD0nJExPQ0FUSU9OJwpleHBvcnQgTU9OSVRPUklOR19ST0xFPXJwCmV4cG9ydCBNT05JVE9SSU5HX1JPTEVfSU5TVEFOQ0U9JyQoaG9zdG5hbWUpJwoKZXhwb3J0IE1EU0RfTVNHUEFDS19TT1JUX0NPTFVNTlM9MQpFT0YKCiMgc2V0dGluZyBNT05JVE9SSU5HX0dDU19BVVRIX0lEX1RZUEU9QXV0aEtleVZhdWx0IHNlZW1zIHRvIGhhdmUgY2F1c2VkIG1kc2Qgbm90CiMgdG8gaG9ub3VyIFNTTF9DRVJUX0ZJTEUgYW55IG1vcmUsIGhlYXZlbiBvbmx5IGtub3dzIHdoeS4KbWtkaXIgLXAgL3Vzci9saWIvc3NsL2NlcnRzCmNzcGxpdCAtZiAvdXNyL2xpYi9zc2wvY2VydHMvY2VydC0gLWIgJTAzZC5wZW0gL2V0Yy9wa2kvdGxzL2NlcnRzL2NhLWJ1bmRsZS5jcnQgL14kLzEgeyp9ID4vZGV2L251bGwKY19yZWhhc2ggL3Vzci9saWIvc3NsL2NlcnRzCgojIHdlIGxlYXZlIGNsaWVudElkIGJsYW5rIGFzIGxvbmcgYXMgb25seSAxIG1hbmFnZWQgaWRlbnRpdHkgYXNzaWduZWQgdG8gdm1zcwojIGlmIHdlIGhhdmUgbW9yZSB0aGFuIDEsIHdlIHdpbGwgbmVlZCB0byBwb3B1bGF0ZSB3aXRoIGNsaWVudElkIHVzZWQgZm9yIG9mZi1ub2RlIHNjYW5uaW5nCmNhdCA+L2V0Yy9kZWZhdWx0L3ZzYS1ub2Rlc2Nhbi1hZ2VudC5jb25maWcgPDxFT0YKewogICAgIk5pY2UiOiAxOSwKICAgICJUaW1lb3V0IjogMTA4MDAsCiAgICAiQ2xpZW50SWQiOiAiIiwKICAgICJUZW5hbnRJZCI6ICIkQVpVUkVTRUNQQUNLVlNBVEVOQU5USUQiLAogICAgIlF1YWx5c1N0b3JlQmFzZVVybCI6ICIkQVpVUkVTRUNQQUNLUVVBTFlTVVJMIiwKICAgICJQcm9jZXNzVGltZW91dCI6IDMwMCwKICAgICJDb21tYW5kRGVsYXkiOiAwCiAgfQpFT0YKCmVjaG8gImVuYWJsaW5nIGFybyBzZXJ2aWNlcyIKZm9yIHNlcnZpY2UgaW4gYXJvLWRidG9rZW4gYXJvLW1vbml0b3IgYXJvLXBvcnRhbCBhcm8tcnAgYXVvbXMgYXpzZWNkIGF6c2VjbW9uZCBtZHNkIG1kbSBjaHJvbnlkIGZsdWVudGJpdDsgZG8KICBzeXN0ZW1jdGwgZW5hYmxlICRzZXJ2aWNlLnNlcnZpY2UKZG9uZQoKZm9yIHNjYW4gaW4gYmFzZWxpbmUgY2xhbWF2IHNvZnR3YXJlOyBkbwogIC91c3IvbG9jYWwvYmluL2F6c2VjZCBjb25maWcgLXMgJHNjYW4gLWQgUDFECmRvbmUKCmVjaG8gInJlYm9vdGluZyIKcmVzdG9yZWNvbiAtUkYgL3Zhci9sb2cvKgooc2xlZXAgMzA7IHJlYm9vdCkgJgo=')))]" + "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==')))]" } } } @@ -678,21 +670,6 @@ "[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, @@ -746,201 +723,210 @@ "location": "[resourceGroup().location]" }, { + "apiVersion": "2023-04-15", "kind": "GlobalDocumentDB", + "location": "[resourceGroup().location]", + "name": "[parameters('databaseAccountName')]", "properties": { - "consistencyPolicy": { - "defaultConsistencyLevel": "Strong" - }, - "locations": [ - { - "locationName": "[resourceGroup().location]" - } - ], - "databaseAccountOfferType": "Standard", - "ipRules": "[if(parameters('disableCosmosDBFirewall'), createArray(), concat(parameters('ipRules'),createArray(createObject('ipAddressOrRange', '104.42.195.92'),createObject('ipAddressOrRange','40.76.54.131'),createObject('ipAddressOrRange','52.176.6.30'),createObject('ipAddressOrRange','52.169.50.45'),createObject('ipAddressOrRange','52.187.184.26'))))]", - "isVirtualNetworkFilterEnabled": "[not(parameters('disableCosmosDBFirewall'))]", - "virtualNetworkRules": "[if(parameters('disableCosmosDBFirewall'), createArray(), variables('rpCosmoDbVirtualNetworkRules'))]", - "disableKeyBasedMetadataWriteAccess": true, "backupPolicy": { "periodicModeProperties": { "backupIntervalInMinutes": 240, "backupRetentionIntervalInHours": 720 }, "type": "Periodic" - } + }, + "consistencyPolicy": { + "defaultConsistencyLevel": "Strong" + }, + "databaseAccountOfferType": "Standard", + "disableKeyBasedMetadataWriteAccess": true, + "ipRules": "[if(parameters('disableCosmosDBFirewall'), createArray(), concat(parameters('ipRules'),createArray(createObject('ipAddressOrRange', '104.42.195.92'),createObject('ipAddressOrRange','40.76.54.131'),createObject('ipAddressOrRange','52.176.6.30'),createObject('ipAddressOrRange','52.169.50.45'),createObject('ipAddressOrRange','52.187.184.26'))))]", + "isVirtualNetworkFilterEnabled": "[not(parameters('disableCosmosDBFirewall'))]", + "locations": [ + { + "locationName": "[resourceGroup().location]" + } + ], + "minimalTlsVersion": "Tls12", + "virtualNetworkRules": "[if(parameters('disableCosmosDBFirewall'), createArray(), variables('rpCosmoDbVirtualNetworkRules'))]" }, - "name": "[parameters('databaseAccountName')]", - "type": "Microsoft.DocumentDB/databaseAccounts", - "location": "[resourceGroup().location]", "tags": { "defaultExperience": "Core (SQL)" }, - "apiVersion": "2021-01-15" + "type": "Microsoft.DocumentDB/databaseAccounts" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO')]", "properties": { - "resource": { - "id": "['ARO']" - }, "options": { "throughput": "[parameters('cosmosDB').standardProvisionedThroughput]" + }, + "resource": { + "id": "['ARO']" } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/AsyncOperations')]", "properties": { + "options": {}, "resource": { + "defaultTtl": 604800, "id": "AsyncOperations", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": 604800 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/AsyncOperations')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + }, + { + "apiVersion": "2023-04-15", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] - }, - { + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/OpenShiftVersions')]", "properties": { + "options": {}, "resource": { + "defaultTtl": -1, "id": "OpenShiftVersions", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/OpenShiftVersions')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + }, + { + "apiVersion": "2023-04-15", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] - }, - { + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/ClusterManagerConfigurations')]", "properties": { + "options": {}, "resource": { "id": "ClusterManagerConfigurations", "partitionKey": { + "kind": "Hash", "paths": [ "/partitionKey" - ], - "kind": "Hash" + ] } - }, - "options": {} + } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/ClusterManagerConfigurations')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + }, + { + "apiVersion": "2023-04-15", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] - }, - { + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Billing')]", "properties": { + "options": {}, "resource": { "id": "Billing", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" + ] } - }, - "options": {} - }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Billing')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", - "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] - }, - { - "properties": { - "resource": { - "id": "Gateway", - "partitionKey": { - "paths": [ - "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": { - "throughput": "[parameters('cosmosDB').gatewayProvisionedThroughput]" } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Gateway')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + }, + { + "apiVersion": "2023-04-15", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] - }, - { + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Gateway')]", "properties": { + "options": { + "throughput": "[parameters('cosmosDB').gatewayProvisionedThroughput]" + }, "resource": { - "id": "Monitors", + "defaultTtl": -1, + "id": "Gateway", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": {} + ] + } + } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Monitors')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + }, + { + "apiVersion": "2023-04-15", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Monitors')]", + "properties": { + "options": {}, + "resource": { + "defaultTtl": -1, + "id": "Monitors", + "partitionKey": { + "kind": "Hash", + "paths": [ + "/id" + ] + } + } + }, + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/OpenShiftClusters')]", "properties": { + "options": {}, "resource": { "id": "OpenShiftClusters", "partitionKey": { + "kind": "Hash", "paths": [ "/partitionKey" - ], - "kind": "Hash" + ] }, "uniqueKeyPolicy": { "uniqueKeys": [ @@ -961,64 +947,56 @@ } ] } - }, - "options": {} - }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/OpenShiftClusters')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", - "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] - }, - { - "properties": { - "resource": { - "id": "Portal", - "partitionKey": { - "paths": [ - "/id" - ], - "kind": "Hash" - }, - "defaultTtl": -1 - }, - "options": { - "throughput": "[parameters('cosmosDB').portalProvisionedThroughput]" } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Portal')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + }, + { + "apiVersion": "2023-04-15", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Portal')]", + "properties": { + "options": { + "throughput": "[parameters('cosmosDB').portalProvisionedThroughput]" + }, + "resource": { + "defaultTtl": -1, + "id": "Portal", + "partitionKey": { + "kind": "Hash", + "paths": [ + "/id" + ] + } + } + }, + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { + "apiVersion": "2023-04-15", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" + ], + "location": "[resourceGroup().location]", + "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Subscriptions')]", "properties": { + "options": {}, "resource": { "id": "Subscriptions", "partitionKey": { + "kind": "Hash", "paths": [ "/id" - ], - "kind": "Hash" + ] } - }, - "options": {} + } }, - "name": "[concat(parameters('databaseAccountName'), '/', 'ARO', '/Subscriptions')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "location": "[resourceGroup().location]", - "apiVersion": "2021-01-15", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), 'ARO')]", - "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" - ] + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" }, { "properties": { diff --git a/pkg/deploy/config.go b/pkg/deploy/config.go index 449cd6990..998d11095 100644 --- a/pkg/deploy/config.go +++ b/pkg/deploy/config.go @@ -48,8 +48,6 @@ 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_dev.go b/pkg/deploy/generator/resources_dev.go index fdd7181fe..668b7fee3 100644 --- a/pkg/deploy/generator/resources_dev.go +++ b/pkg/deploy/generator/resources_dev.go @@ -53,7 +53,7 @@ func (g *generator) devProxyVMSS() *arm.Resource { }, VirtualMachineScaleSetProperties: &mgmtcompute.VirtualMachineScaleSetProperties{ UpgradePolicy: &mgmtcompute.UpgradePolicy{ - Mode: mgmtcompute.UpgradeModeManual, + Mode: mgmtcompute.UpgradeModeRolling, }, VirtualMachineProfile: &mgmtcompute.VirtualMachineScaleSetVMProfile{ OsProfile: &mgmtcompute.VirtualMachineScaleSetOSProfile{ diff --git a/pkg/deploy/generator/resources_gateway.go b/pkg/deploy/generator/resources_gateway.go index 63e7f1dbf..dc5ae15bb 100644 --- a/pkg/deploy/generator/resources_gateway.go +++ b/pkg/deploy/generator/resources_gateway.go @@ -259,7 +259,7 @@ func (g *generator) gatewayVMSS() *arm.Resource { Tags: map[string]*string{}, VirtualMachineScaleSetProperties: &mgmtcompute.VirtualMachineScaleSetProperties{ UpgradePolicy: &mgmtcompute.UpgradePolicy{ - Mode: mgmtcompute.UpgradeModeManual, + Mode: mgmtcompute.UpgradeModeRolling, }, VirtualMachineProfile: &mgmtcompute.VirtualMachineScaleSetVMProfile{ OsProfile: &mgmtcompute.VirtualMachineScaleSetOSProfile{ diff --git a/pkg/deploy/generator/resources_rp.go b/pkg/deploy/generator/resources_rp.go index ac4aa4d94..38f959042 100644 --- a/pkg/deploy/generator/resources_rp.go +++ b/pkg/deploy/generator/resources_rp.go @@ -8,8 +8,8 @@ import ( "fmt" "strings" + sdkcosmos "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2" mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" - mgmtdocumentdb "github.com/Azure/azure-sdk-for-go/services/cosmos-db/mgmt/2021-01-15/documentdb" mgmtkeyvault "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2019-09-01/keyvault" mgmtmsi "github.com/Azure/azure-sdk-for-go/services/msi/mgmt/2018-11-30/msi" mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" @@ -457,7 +457,6 @@ func (g *generator) rpVMSS() *arm.Resource { "azureCloudName", "azureSecPackQualysUrl", "azureSecPackVSATenantId", - "billingE2EStorageAccountId", "clusterMdmAccount", "clusterMdsdAccount", "clusterMdsdConfigVersion", @@ -548,7 +547,7 @@ func (g *generator) rpVMSS() *arm.Resource { Tags: map[string]*string{}, VirtualMachineScaleSetProperties: &mgmtcompute.VirtualMachineScaleSetProperties{ UpgradePolicy: &mgmtcompute.UpgradePolicy{ - Mode: mgmtcompute.UpgradeModeManual, + Mode: mgmtcompute.UpgradeModeRolling, }, VirtualMachineProfile: &mgmtcompute.VirtualMachineScaleSetVMProfile{ OsProfile: &mgmtcompute.VirtualMachineScaleSetOSProfile{ @@ -978,25 +977,31 @@ func (g *generator) rpServiceKeyvault() *arm.Resource { } func (g *generator) rpCosmosDB() []*arm.Resource { - cosmosdb := &mgmtdocumentdb.DatabaseAccountCreateUpdateParameters{ - Kind: mgmtdocumentdb.GlobalDocumentDB, - DatabaseAccountCreateUpdateProperties: &mgmtdocumentdb.DatabaseAccountCreateUpdateProperties{ - ConsistencyPolicy: &mgmtdocumentdb.ConsistencyPolicy{ - DefaultConsistencyLevel: mgmtdocumentdb.Strong, + dbType := sdkcosmos.DatabaseAccountKindGlobalDocumentDB + consistency := sdkcosmos.DefaultConsistencyLevelStrong + backupPolicy := sdkcosmos.BackupPolicyTypePeriodic + minTLSVersion := sdkcosmos.MinimalTLSVersionTls12 + + cosmosdb := &sdkcosmos.DatabaseAccountCreateUpdateParameters{ + Kind: &dbType, + Properties: &sdkcosmos.DatabaseAccountCreateUpdateProperties{ + ConsistencyPolicy: &sdkcosmos.ConsistencyPolicy{ + DefaultConsistencyLevel: &consistency, }, - Locations: &[]mgmtdocumentdb.Location{ + Locations: []*sdkcosmos.Location{ { LocationName: to.StringPtr("[resourceGroup().location]"), }, }, - DatabaseAccountOfferType: to.StringPtr(string(mgmtdocumentdb.Standard)), - BackupPolicy: mgmtdocumentdb.PeriodicModeBackupPolicy{ - Type: mgmtdocumentdb.TypePeriodic, - PeriodicModeProperties: &mgmtdocumentdb.PeriodicModeProperties{ + DatabaseAccountOfferType: to.StringPtr("Standard"), + BackupPolicy: &sdkcosmos.PeriodicModeBackupPolicy{ + Type: &backupPolicy, + PeriodicModeProperties: &sdkcosmos.PeriodicModeProperties{ BackupIntervalInMinutes: to.Int32Ptr(240), //4 hours BackupRetentionIntervalInHours: to.Int32Ptr(720), //30 days }, }, + MinimalTLSVersion: &minTLSVersion, }, Name: to.StringPtr("[parameters('databaseAccountName')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts"), @@ -1009,12 +1014,13 @@ func (g *generator) rpCosmosDB() []*arm.Resource { r := &arm.Resource{ Resource: cosmosdb, APIVersion: azureclient.APIVersion("Microsoft.DocumentDB"), + Type: "Microsoft.DocumentDB/databaseAccounts", } if g.production { - cosmosdb.IPRules = &[]mgmtdocumentdb.IPAddressOrRange{} - cosmosdb.IsVirtualNetworkFilterEnabled = to.BoolPtr(true) - cosmosdb.VirtualNetworkRules = &[]mgmtdocumentdb.VirtualNetworkRule{} - cosmosdb.DisableKeyBasedMetadataWriteAccess = to.BoolPtr(true) + cosmosdb.Properties.IPRules = []*sdkcosmos.IPAddressOrRange{} + cosmosdb.Properties.IsVirtualNetworkFilterEnabled = to.BoolPtr(true) + cosmosdb.Properties.VirtualNetworkRules = []*sdkcosmos.VirtualNetworkRule{} + cosmosdb.Properties.DisableKeyBasedMetadataWriteAccess = to.BoolPtr(true) } rs := []*arm.Resource{ @@ -1031,12 +1037,12 @@ func (g *generator) rpCosmosDB() []*arm.Resource { func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Resource { database := &arm.Resource{ - Resource: &mgmtdocumentdb.SQLDatabaseCreateUpdateParameters{ - SQLDatabaseCreateUpdateProperties: &mgmtdocumentdb.SQLDatabaseCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLDatabaseResource{ + Resource: &sdkcosmos.SQLDatabaseCreateUpdateParameters{ + Properties: &sdkcosmos.SQLDatabaseCreateUpdateProperties{ + Resource: &sdkcosmos.SQLDatabaseResource{ ID: to.StringPtr("[" + databaseName + "]"), }, - Options: &mgmtdocumentdb.CreateUpdateOptions{ + Options: &sdkcosmos.CreateUpdateOptions{ Throughput: to.Int32Ptr(cosmosDbStandardProvisionedThroughputHack), }, }, @@ -1045,21 +1051,23 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso Location: to.StringPtr("[resourceGroup().location]"), }, APIVersion: azureclient.APIVersion("Microsoft.DocumentDB"), + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", } + hashPartitionKey := sdkcosmos.PartitionKindHash portal := &arm.Resource{ - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("Portal"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, DefaultTTL: to.Int32Ptr(-1), }, - Options: &mgmtdocumentdb.CreateUpdateOptions{ + Options: &sdkcosmos.CreateUpdateOptions{ Throughput: to.Int32Ptr(cosmosDbPortalProvisionedThroughputHack), }, }, @@ -1071,22 +1079,23 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", } gateway := &arm.Resource{ - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("Gateway"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, DefaultTTL: to.Int32Ptr(-1), }, - Options: &mgmtdocumentdb.CreateUpdateOptions{ + Options: &sdkcosmos.CreateUpdateOptions{ Throughput: to.Int32Ptr(cosmosDbGatewayProvisionedThroughputHack), }, }, @@ -1098,34 +1107,35 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", } if !g.production { - database.Resource.(*mgmtdocumentdb.SQLDatabaseCreateUpdateParameters).SQLDatabaseCreateUpdateProperties.Options = &mgmtdocumentdb.CreateUpdateOptions{ - AutoscaleSettings: &mgmtdocumentdb.AutoscaleSettings{ + database.Resource.(*sdkcosmos.SQLDatabaseCreateUpdateParameters).Properties.Options = &sdkcosmos.CreateUpdateOptions{ + AutoscaleSettings: &sdkcosmos.AutoscaleSettings{ MaxThroughput: to.Int32Ptr(1000), }, } - portal.Resource.(*mgmtdocumentdb.SQLContainerCreateUpdateParameters).SQLContainerCreateUpdateProperties.Options = &mgmtdocumentdb.CreateUpdateOptions{} - gateway.Resource.(*mgmtdocumentdb.SQLContainerCreateUpdateParameters).SQLContainerCreateUpdateProperties.Options = &mgmtdocumentdb.CreateUpdateOptions{} + portal.Resource.(*sdkcosmos.SQLContainerCreateUpdateParameters).Properties.Options = &sdkcosmos.CreateUpdateOptions{} + gateway.Resource.(*sdkcosmos.SQLContainerCreateUpdateParameters).Properties.Options = &sdkcosmos.CreateUpdateOptions{} } rs := []*arm.Resource{ database, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("AsyncOperations"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, DefaultTTL: to.Int32Ptr(7 * 86400), // 7 days }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/AsyncOperations')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1135,21 +1145,22 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("OpenShiftVersions"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, DefaultTTL: to.Int32Ptr(-1), }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/OpenShiftVersions')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1159,20 +1170,21 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("ClusterManagerConfigurations"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/partitionKey", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/partitionKey"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/ClusterManagerConfigurations')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1182,20 +1194,21 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("Billing"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/Billing')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1205,22 +1218,23 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, gateway, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("Monitors"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, DefaultTTL: to.Int32Ptr(-1), }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/Monitors')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1230,39 +1244,40 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("OpenShiftClusters"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/partitionKey", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/partitionKey"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, - UniqueKeyPolicy: &mgmtdocumentdb.UniqueKeyPolicy{ - UniqueKeys: &[]mgmtdocumentdb.UniqueKey{ + UniqueKeyPolicy: &sdkcosmos.UniqueKeyPolicy{ + UniqueKeys: []*sdkcosmos.UniqueKey{ { - Paths: &[]string{ - "/key", + Paths: []*string{ + to.StringPtr("/key"), }, }, { - Paths: &[]string{ - "/clusterResourceGroupIdKey", + Paths: []*string{ + to.StringPtr("/clusterResourceGroupIdKey"), }, }, { - Paths: &[]string{ - "/clientIdKey", + Paths: []*string{ + to.StringPtr("/clientIdKey"), }, }, }, }, }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/OpenShiftClusters')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1272,21 +1287,22 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, portal, { - Resource: &mgmtdocumentdb.SQLContainerCreateUpdateParameters{ - SQLContainerCreateUpdateProperties: &mgmtdocumentdb.SQLContainerCreateUpdateProperties{ - Resource: &mgmtdocumentdb.SQLContainerResource{ + Resource: &sdkcosmos.SQLContainerCreateUpdateParameters{ + Properties: &sdkcosmos.SQLContainerCreateUpdateProperties{ + Resource: &sdkcosmos.SQLContainerResource{ ID: to.StringPtr("Subscriptions"), - PartitionKey: &mgmtdocumentdb.ContainerPartitionKey{ - Paths: &[]string{ - "/id", + PartitionKey: &sdkcosmos.ContainerPartitionKey{ + Paths: []*string{ + to.StringPtr("/id"), }, - Kind: mgmtdocumentdb.PartitionKindHash, + Kind: &hashPartitionKey, }, }, - Options: &mgmtdocumentdb.CreateUpdateOptions{}, + Options: &sdkcosmos.CreateUpdateOptions{}, }, Name: to.StringPtr("[concat(parameters('databaseAccountName'), '/', " + databaseName + ", '/Subscriptions')]"), Type: to.StringPtr("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers"), @@ -1296,6 +1312,7 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso DependsOn: []string{ "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), " + databaseName + ")]", }, + Type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", }, } @@ -1426,19 +1443,6 @@ 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/devProxyVMSS.sh b/pkg/deploy/generator/scripts/devProxyVMSS.sh index bdbb8fe35..cb8e15bf8 100644 --- a/pkg/deploy/generator/scripts/devProxyVMSS.sh +++ b/pkg/deploy/generator/scripts/devProxyVMSS.sh @@ -1,20 +1,20 @@ #Adding retry logic to yum commands in order to avoid stalling out on resource locks echo "running RHUI fix" -for attempt in {1..5}; do +for attempt in {1..60}; do yum update -y --disablerepo='*' --enablerepo='rhui-microsoft-azure*' && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "running yum update" -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y -x WALinuxAgent -x WALinuxAgent-udev update --allowerasing && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "installing podman-docker" -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y install podman-docker && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done firewall-cmd --add-port=443/tcp --permanent diff --git a/pkg/deploy/generator/scripts/gatewayVMSS.sh b/pkg/deploy/generator/scripts/gatewayVMSS.sh index 4034eed36..2f9b09efe 100644 --- a/pkg/deploy/generator/scripts/gatewayVMSS.sh +++ b/pkg/deploy/generator/scripts/gatewayVMSS.sh @@ -7,15 +7,15 @@ systemctl reload sshd.service #Adding retry logic to yum commands in order to avoid stalling out on resource locks echo "running RHUI fix" -for attempt in {1..5}; do +for attempt in {1..60}; do yum update -y --disablerepo='*' --enablerepo='rhui-microsoft-azure*' && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "running yum update" -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y -x WALinuxAgent -x WALinuxAgent-udev update --allowerasing && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "extending partition table" @@ -34,9 +34,9 @@ xfs_growfs /var rpm --import https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8 rpm --import https://packages.microsoft.com/keys/microsoft.asc -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "configuring logrotate" @@ -111,10 +111,10 @@ EOF semanage fcontext -a -t var_log_t "/var/log/journal(/.*)?" mkdir -p /var/log/journal -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y install clamav azsec-clamav azsec-monitor azure-cli azure-mdsd azure-security podman-docker openssl-perl python3 && break # hack - we are installing python3 on hosts due to an issue with Azure Linux Extensions https://github.com/Azure/azure-linux-extensions/pull/1505 - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "applying firewall rules" diff --git a/pkg/deploy/generator/scripts/rpVMSS.sh b/pkg/deploy/generator/scripts/rpVMSS.sh index 290b534c1..78149f304 100644 --- a/pkg/deploy/generator/scripts/rpVMSS.sh +++ b/pkg/deploy/generator/scripts/rpVMSS.sh @@ -7,15 +7,15 @@ systemctl reload sshd.service #Adding retry logic to yum commands in order to avoid stalling out on resource locks echo "running RHUI fix" -for attempt in {1..5}; do +for attempt in {1..60}; do yum update -y --disablerepo='*' --enablerepo='rhui-microsoft-azure*' && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "running yum update" -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y -x WALinuxAgent -x WALinuxAgent-udev update --allowerasing && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "extending partition table" @@ -35,9 +35,9 @@ echo "importing rpm repositories" rpm --import https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8 rpm --import https://packages.microsoft.com/keys/microsoft.asc -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && break - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done echo "configuring logrotate" @@ -95,10 +95,10 @@ EOF semanage fcontext -a -t var_log_t "/var/log/journal(/.*)?" mkdir -p /var/log/journal -for attempt in {1..5}; do +for attempt in {1..60}; do yum -y install clamav azsec-clamav azsec-monitor azure-cli azure-mdsd azure-security podman podman-docker openssl-perl python3 && break # hack - we are installing python3 on hosts due to an issue with Azure Linux Extensions https://github.com/Azure/azure-linux-extensions/pull/1505 - if [[ ${attempt} -lt 5 ]]; then sleep 10; else exit 1; fi + if [[ ${attempt} -lt 60 ]]; then sleep 30; else exit 1; fi done # https://access.redhat.com/security/cve/cve-2020-13401 @@ -272,7 +272,6 @@ 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' @@ -312,7 +311,6 @@ 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 7452d0fb2..d5506af64 100644 --- a/pkg/deploy/generator/templates_rp.go +++ b/pkg/deploy/generator/templates_rp.go @@ -41,8 +41,6 @@ func (g *generator) rpTemplate() *arm.Template { "azureCloudName", "azureSecPackQualysUrl", "azureSecPackVSATenantId", - "billingE2EStorageAccountId", - "billingServicePrincipalId", "clusterMdmAccount", "clusterMdsdAccount", "clusterMdsdConfigVersion", @@ -100,8 +98,6 @@ func (g *generator) rpTemplate() *arm.Template { case "armApiCaBundle", "armApiClientCertCommonName", "armClientId", - "billingServicePrincipalId", - "billingE2EStorageAccountId", "gatewayDomains", "rpFeatures": p.DefaultValue = "" @@ -177,7 +173,6 @@ 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 3fe18ab61..9390a1f8f 100644 --- a/pkg/frontend/openshiftcluster_putorpatch.go +++ b/pkg/frontend/openshiftcluster_putorpatch.go @@ -149,9 +149,10 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, log *logrus. // Patch should be used for updating individual fields of the document. case http.MethodPatch: ext = converter.ToExternal(doc.OpenShiftCluster) - converter.ExternalNoReadOnly(ext) } + converter.ExternalNoReadOnly(ext) + err = json.Unmarshal(body, &ext) if err != nil { return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidRequestContent, "", "The request content was invalid and could not be deserialized: %q.", err) diff --git a/pkg/frontend/subscriptions_put.go b/pkg/frontend/subscriptions_put.go index 42d6a345a..84d530884 100644 --- a/pkg/frontend/subscriptions_put.go +++ b/pkg/frontend/subscriptions_put.go @@ -51,11 +51,6 @@ func (f *frontend) _putSubscription(ctx context.Context, r *http.Request) ([]byt oldState := doc.Subscription.State h := &codec.JsonHandle{ - BasicHandle: codec.BasicHandle{ - DecodeOptions: codec.DecodeOptions{ - ErrorIfNoField: true, - }, - }, Indent: 4, } diff --git a/pkg/util/arm/marshal.go b/pkg/util/arm/marshal.go index 58929d5a6..69bf5fdd8 100644 --- a/pkg/util/arm/marshal.go +++ b/pkg/util/arm/marshal.go @@ -7,13 +7,47 @@ import ( "encoding/json" "fmt" "reflect" + "strings" + sdkcosmos "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2" gofrsuuid "github.com/gofrs/uuid" ) // MarshalJSON marshals the nested r.Resource ignoring any MarshalJSON() methods // on its types. It then merges remaining fields of r over the result func (r *Resource) MarshalJSON() ([]byte, error) { + var b []byte + var err error + + // hack to handle newer track2 sdk which doesn't have json tags + if strings.HasPrefix(r.Type, "Microsoft.DocumentDB/databaseAccounts/sqlDatabases") { + if reflect.TypeOf(r.Resource) == reflect.TypeOf(&sdkcosmos.SQLDatabaseCreateUpdateParameters{}) { + b, err = r.Resource.(*sdkcosmos.SQLDatabaseCreateUpdateParameters).MarshalJSON() + } else if reflect.TypeOf(r.Resource) == reflect.TypeOf(&sdkcosmos.SQLContainerCreateUpdateParameters{}) { + b, err = r.Resource.(*sdkcosmos.SQLContainerCreateUpdateParameters).MarshalJSON() + } + } else if strings.HasPrefix(r.Type, "Microsoft.DocumentDB/databaseAccounts") { + b, err = r.Resource.(*sdkcosmos.DatabaseAccountCreateUpdateParameters).MarshalJSON() + } + + if err != nil { + return b, err + } + + if b != nil { + dataMap := map[string]interface{}{} + err = json.Unmarshal(b, &dataMap) + if err != nil { + return nil, err + } + + dataMap["apiVersion"] = r.APIVersion + if r.DependsOn != nil { + dataMap["dependsOn"] = r.DependsOn + } + return json.Marshal(dataMap) + } + resource := reflect.ValueOf(shadowCopy(r.Resource)) outer := reflect.ValueOf(*r) diff --git a/pkg/util/azureclient/apiversions.go b/pkg/util/azureclient/apiversions.go index b80bb5afc..3ad8deffd 100644 --- a/pkg/util/azureclient/apiversions.go +++ b/pkg/util/azureclient/apiversions.go @@ -19,7 +19,7 @@ var apiVersions = map[string]string{ "microsoft.compute/snapshots": "2020-05-01", "microsoft.containerregistry": "2020-11-01-preview", "microsoft.resources/deployments": "2021-04-01", - "microsoft.documentdb": "2021-01-15", + "microsoft.documentdb": "2023-04-15", "microsoft.insights": "2018-03-01", "microsoft.keyvault": "2019-09-01", "microsoft.keyvault/vaults/accesspolicies": "2021-10-01", diff --git a/pkg/util/azureclient/azuresdk/armkeyvault/vaults.go b/pkg/util/azureclient/azuresdk/armkeyvault/vaults.go new file mode 100644 index 000000000..36a8c906e --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armkeyvault/vaults.go @@ -0,0 +1,30 @@ +package armkeyvault + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" + + "github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/azcore" +) + +type VaultsClient interface { + CheckNameAvailability(ctx context.Context, vaultName armkeyvault.VaultCheckNameAvailabilityParameters, options *armkeyvault.VaultsClientCheckNameAvailabilityOptions) (armkeyvault.VaultsClientCheckNameAvailabilityResponse, error) +} + +type vaultsClient struct { + *armkeyvault.VaultsClient +} + +var _ VaultsClient = &vaultsClient{} + +func NewVaultsClient(subscriptionID string, credential azcore.TokenCredential, options *arm.ClientOptions) (VaultsClient, error) { + client, err := armkeyvault.NewVaultsClient(subscriptionID, credential, options) + return vaultsClient{ + VaultsClient: client, + }, err +} diff --git a/pkg/util/azureclient/mgmt/authorization/generate.go b/pkg/util/azureclient/mgmt/authorization/generate.go index 9072a9836..d894dcbfa 100644 --- a/pkg/util/azureclient/mgmt/authorization/generate.go +++ b/pkg/util/azureclient/mgmt/authorization/generate.go @@ -4,5 +4,5 @@ package authorization // Licensed under the Apache License 2.0. //go:generate rm -rf ../../../../util/mocks/$GOPACKAGE -//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/$GOPACKAGE PermissionsClient,RoleAssignmentsClient,DenyAssignmentClient,RoleDefinitionsClient +//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/$GOPACKAGE RoleAssignmentsClient,DenyAssignmentClient,RoleDefinitionsClient //go:generate go run ../../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/util/azureclient/mgmt/authorization/permissions.go b/pkg/util/azureclient/mgmt/authorization/permissions.go deleted file mode 100644 index 478c59317..000000000 --- a/pkg/util/azureclient/mgmt/authorization/permissions.go +++ /dev/null @@ -1,32 +0,0 @@ -package authorization - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" - "github.com/Azure/go-autorest/autorest" - - "github.com/Azure/ARO-RP/pkg/util/azureclient" -) - -// PermissionsClient is a minimal interface for azure PermissionsClient -type PermissionsClient interface { - PermissionsClientAddons -} - -type permissionsClient struct { - mgmtauthorization.PermissionsClient -} - -var _ PermissionsClient = &permissionsClient{} - -// NewPermissionsClient creates a new PermissionsClient -func NewPermissionsClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) PermissionsClient { - client := mgmtauthorization.NewPermissionsClientWithBaseURI(environment.ResourceManagerEndpoint, subscriptionID) - client.Authorizer = authorizer - - return &permissionsClient{ - PermissionsClient: client, - } -} diff --git a/pkg/util/azureclient/mgmt/authorization/permissions_addons.go b/pkg/util/azureclient/mgmt/authorization/permissions_addons.go deleted file mode 100644 index ca4251861..000000000 --- a/pkg/util/azureclient/mgmt/authorization/permissions_addons.go +++ /dev/null @@ -1,60 +0,0 @@ -package authorization - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "context" - - mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" -) - -// PermissionsClientAddons contains addons for PermissionsClient -type PermissionsClientAddons interface { - ListForResource(ctx context.Context, resourceGroupName string, resourceProviderNamespace string, parentResourcePath string, resourceType string, resourceName string) (permissions []mgmtauthorization.Permission, err error) - ListForResourceGroup(ctx context.Context, resourceGroupName string) (permissions []mgmtauthorization.Permission, err error) -} - -func (c *permissionsClient) ListForResource(ctx context.Context, resourceGroupName string, resourceProviderNamespace string, parentResourcePath string, resourceType string, resourceName string) (permissions []mgmtauthorization.Permission, err error) { - page, err := c.PermissionsClient.ListForResource(ctx, resourceGroupName, resourceProviderNamespace, parentResourcePath, resourceType, resourceName) - if err != nil { - return nil, err - } - - for { - permissions = append(permissions, page.Values()...) - - err = page.NextWithContext(ctx) - if err != nil { - return nil, err - } - - if !page.NotDone() { - break - } - } - - return permissions, nil -} - -func (c *permissionsClient) ListForResourceGroup(ctx context.Context, resourceGroupName string) (permissions []mgmtauthorization.Permission, error error) { - page, err := c.PermissionsClient.ListForResourceGroup(ctx, resourceGroupName) - if err != nil { - return nil, err - } - - for { - permissions = append(permissions, page.Values()...) - - err = page.NextWithContext(ctx) - if err != nil { - return nil, err - } - - if !page.NotDone() { - break - } - } - - return permissions, nil -} diff --git a/pkg/util/azureclient/mgmt/compute/virtualmachines.go b/pkg/util/azureclient/mgmt/compute/virtualmachines.go index 4988cc3d8..860d5d811 100644 --- a/pkg/util/azureclient/mgmt/compute/virtualmachines.go +++ b/pkg/util/azureclient/mgmt/compute/virtualmachines.go @@ -5,6 +5,7 @@ package compute import ( "context" + "time" mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" "github.com/Azure/go-autorest/autorest" @@ -28,6 +29,7 @@ var _ VirtualMachinesClient = &virtualMachinesClient{} func NewVirtualMachinesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) VirtualMachinesClient { client := mgmtcompute.NewVirtualMachinesClientWithBaseURI(environment.ResourceManagerEndpoint, subscriptionID) client.Authorizer = authorizer + client.PollingDuration = 30 * time.Minute return &virtualMachinesClient{ VirtualMachinesClient: client, diff --git a/pkg/util/azureclient/mgmt/keyvault/vaults.go b/pkg/util/azureclient/mgmt/keyvault/vaults.go deleted file mode 100644 index 2d2ebf52c..000000000 --- a/pkg/util/azureclient/mgmt/keyvault/vaults.go +++ /dev/null @@ -1,34 +0,0 @@ -package keyvault - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "context" - - mgmtkeyvault "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2019-09-01/keyvault" - "github.com/Azure/go-autorest/autorest" - - "github.com/Azure/ARO-RP/pkg/util/azureclient" -) - -// VaultsClient is a minimal interface for azure VaultsClient -type VaultsClient interface { - CheckNameAvailability(ctx context.Context, vaultName mgmtkeyvault.VaultCheckNameAvailabilityParameters) (result mgmtkeyvault.CheckNameAvailabilityResult, err error) -} - -type vaultsClient struct { - mgmtkeyvault.VaultsClient -} - -var _ VaultsClient = &vaultsClient{} - -// NewVaultsClient creates a new KeyvaultClient -func NewVaultsClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) VaultsClient { - client := mgmtkeyvault.NewVaultsClientWithBaseURI(environment.ResourceManagerEndpoint, subscriptionID) - client.Authorizer = authorizer - - return &vaultsClient{ - VaultsClient: client, - } -} diff --git a/pkg/util/billing/billing.go b/pkg/util/billing/billing.go index a8e97b8a2..935f9abbe 100644 --- a/pkg/util/billing/billing.go +++ b/pkg/util/billing/billing.go @@ -4,28 +4,15 @@ 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 { @@ -34,58 +21,19 @@ type Manager interface { } type manager struct { - storageClient *azstorage.Client - billingDB database.Billing - subDB database.Subscriptions - log *logrus.Entry + billingDB database.Billing + 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{ - storageClient: storageClient, - subDB: sub, - billingDB: billing, - log: log, + 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 { - billingDoc, err := m.billingDB.Create(ctx, &api.BillingDocument{ + _, err := m.billingDB.Create(ctx, &api.BillingDocument{ ID: doc.ID, Key: doc.Key, ClusterResourceGroupIDKey: doc.ClusterResourceGroupIDKey, @@ -104,16 +52,12 @@ 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") - billingDoc, err := m.billingDB.MarkForDeletion(ctx, doc.ID) + _, err := m.billingDB.MarkForDeletion(ctx, doc.ID) if cosmosdb.IsErrorStatusCode(err, http.StatusNotFound) { return nil } @@ -121,67 +65,5 @@ 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/billing/billing_test.go b/pkg/util/billing/billing_test.go index 5cd23eda0..dca66e6f9 100644 --- a/pkg/util/billing/billing_test.go +++ b/pkg/util/billing/billing_test.go @@ -19,101 +19,6 @@ 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() @@ -220,7 +125,6 @@ func TestDelete(t *testing.T) { m := &manager{ log: log, billingDB: billingDatabase, - subDB: subscriptionsDatabase, } err = m.Delete(ctx, &api.OpenShiftClusterDocument{ID: docID}) @@ -279,12 +183,6 @@ func TestEnsure(t *testing.T) { ID: subID, Subscription: &api.Subscription{ Properties: &api.SubscriptionProperties{ - RegisteredFeatures: []api.RegisteredFeatureProfile{ - { - Name: api.FeatureFlagSaveAROTestConfig, - State: "NotRegistered", - }, - }, TenantID: tenantID, }, }, @@ -321,12 +219,6 @@ func TestEnsure(t *testing.T) { ID: subID, Subscription: &api.Subscription{ Properties: &api.SubscriptionProperties{ - RegisteredFeatures: []api.RegisteredFeatureProfile{ - { - Name: api.FeatureFlagSaveAROTestConfig, - State: "NotRegistered", - }, - }, TenantID: tenantID, }, }, @@ -353,12 +245,6 @@ func TestEnsure(t *testing.T) { ID: subID, Subscription: &api.Subscription{ Properties: &api.SubscriptionProperties{ - RegisteredFeatures: []api.RegisteredFeatureProfile{ - { - Name: api.FeatureFlagSaveAROTestConfig, - State: "NotRegistered", - }, - }, TenantID: tenantID, }, }, @@ -408,7 +294,6 @@ 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 80f67b644..1a7d8d4d4 100644 --- a/pkg/util/clienthelper/clienthelper_test.go +++ b/pkg/util/clienthelper/clienthelper_test.go @@ -45,7 +45,7 @@ func TestEnsureDeleted(t *testing.T) { }) client := testclienthelper.NewHookingClient(builder.Build()). - WithDeleteHook(func(obj client.Object) error { + WithPreDeleteHook(func(obj client.Object) error { if obj.GetName() == "test-name-2" { return fmt.Errorf("error on %s", obj.GetName()) } @@ -70,7 +70,7 @@ func TestEnsureDeleted(t *testing.T) { Namespace: "test-ns-2", }) if err == nil { - t.Errorf("function should handle failure response (non-404) correctly") + t.Error(fmt.Errorf("function should handle failure response (non-404) correctly: %w", err)) } err = ch.EnsureDeleted(ctx, schema.GroupVersionKind{Group: "core", Version: "v1", Kind: "ConfigMap"}, diff --git a/pkg/util/cluster/cluster.go b/pkg/util/cluster/cluster.go index 18ae61849..a25537fa6 100644 --- a/pkg/util/cluster/cluster.go +++ b/pkg/util/cluster/cluster.go @@ -8,6 +8,7 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "fmt" "math/rand" "net/http" @@ -15,8 +16,10 @@ import ( "strings" "time" + armsdk "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - mgmtkeyvault "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2019-09-01/keyvault" + sdkkeyvault "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features" @@ -34,9 +37,9 @@ import ( "github.com/Azure/ARO-RP/pkg/deploy/generator" "github.com/Azure/ARO-RP/pkg/env" "github.com/Azure/ARO-RP/pkg/util/arm" + "github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armkeyvault" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features" - keyvaultclient "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/keyvault" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network" redhatopenshift20200430 "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift/2020-04-30/redhatopenshift" redhatopenshift20210901preview "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift/2021-09-01-preview/redhatopenshift" @@ -67,20 +70,7 @@ type Cluster struct { roleassignments authorization.RoleAssignmentsClient peerings network.VirtualNetworkPeeringsClient ciParentVnetPeerings network.VirtualNetworkPeeringsClient - vaultsClient keyvaultclient.VaultsClient -} - -type errors []error - -func (errs errors) Error() string { - var sb strings.Builder - - for _, err := range errs { - sb.WriteString(err.Error()) - sb.WriteByte('\n') - } - - return sb.String() + vaultsClient armkeyvault.VaultsClient } func New(log *logrus.Entry, environment env.Core, ci bool) (*Cluster, error) { @@ -91,6 +81,7 @@ func New(log *logrus.Entry, environment env.Core, ci bool) (*Cluster, error) { } options := environment.Environment().EnvironmentCredentialOptions() + spTokenCredential, err := azidentity.NewEnvironmentCredential(options) if err != nil { return nil, err @@ -104,6 +95,17 @@ func New(log *logrus.Entry, environment env.Core, ci bool) (*Cluster, error) { scopes := []string{environment.Environment().ResourceManagerScope} authorizer := azidext.NewTokenCredentialAdapter(spTokenCredential, scopes) + armOption := armsdk.ClientOptions{ + ClientOptions: policy.ClientOptions{ + Cloud: options.Cloud, + }, + } + + vaultClient, err := armkeyvault.NewVaultsClient(environment.SubscriptionID(), spTokenCredential, &armOption) + + if err != nil { + return nil, err + } c := &Cluster{ log: log, env: environment, @@ -121,7 +123,7 @@ func New(log *logrus.Entry, environment env.Core, ci bool) (*Cluster, error) { routetables: network.NewRouteTablesClient(environment.Environment(), environment.SubscriptionID(), authorizer), roleassignments: authorization.NewRoleAssignmentsClient(environment.Environment(), environment.SubscriptionID(), authorizer), peerings: network.NewVirtualNetworkPeeringsClient(environment.Environment(), environment.SubscriptionID(), authorizer), - vaultsClient: keyvaultclient.NewVaultsClient(environment.Environment(), environment.SubscriptionID(), authorizer), + vaultsClient: vaultClient, } if ci && env.IsLocalDevelopmentMode() { @@ -232,7 +234,8 @@ func (c *Cluster) Create(ctx context.Context, vnetResourceGroup, clusterName str if c.ci { // name is limited to 24 characters, but must be globally unique, so we generate one and try if it is available kvName = "kv-" + uuid.DefaultGenerator.Generate()[:21] - result, err := c.vaultsClient.CheckNameAvailability(ctx, mgmtkeyvault.VaultCheckNameAvailabilityParameters{Name: &kvName, Type: to.StringPtr("Microsoft.KeyVault/vaults")}) + + result, err := c.vaultsClient.CheckNameAvailability(ctx, sdkkeyvault.VaultCheckNameAvailabilityParameters{Name: &kvName, Type: to.StringPtr("Microsoft.KeyVault/vaults")}, nil) if err != nil { return err } @@ -381,75 +384,28 @@ func (c *Cluster) generateSubnets() (vnetPrefix string, masterSubnet string, wor } func (c *Cluster) Delete(ctx context.Context, vnetResourceGroup, clusterName string) error { - var errs errors + var errs []error - oc, err := c.openshiftclustersv20200430.Get(ctx, vnetResourceGroup, clusterName) - if err == nil { - c.log.Print("deleting role assignments") - err = c.deleteRoleAssignments(ctx, vnetResourceGroup, *oc.OpenShiftClusterProperties.ServicePrincipalProfile.ClientID) - if err != nil { - errs = append(errs, err) - } - - c.log.Print("deleting cluster") - err = c.openshiftclustersv20200430.DeleteAndWait(ctx, vnetResourceGroup, clusterName) - if err != nil { - errs = append(errs, err) - } - } - - if c.ci { - _, err = c.groups.Get(ctx, vnetResourceGroup) - if err == nil { - c.log.Print("deleting resource group") - err = c.groups.DeleteAndWait(ctx, vnetResourceGroup) - if err != nil { - errs = append(errs, err) - } - } - // Only delete peering if CI=true and RP_MODE=development - if env.IsLocalDevelopmentMode() { - r, err := azure.ParseResourceID(c.ciParentVnet) - if err == nil { - err = c.ciParentVnetPeerings.DeleteAndWait(ctx, r.ResourceGroup, r.ResourceName, vnetResourceGroup+"-peer") - } - if err != nil { - errs = append(errs, err) - } - } - } else { - // Deleting the deployment does not clean up the associated resources - c.log.Info("deleting deployment") - err = c.deployments.DeleteAndWait(ctx, vnetResourceGroup, clusterName) - if err != nil { - errs = append(errs, err) - } - - c.log.Info("deleting master/worker subnets") - err = c.subnets.DeleteAndWait(ctx, vnetResourceGroup, "dev-vnet", clusterName+"-master") - if err != nil { - errs = append(errs, err) - } - - err = c.subnets.DeleteAndWait(ctx, vnetResourceGroup, "dev-vnet", clusterName+"-worker") - if err != nil { - errs = append(errs, err) - } - - c.log.Info("deleting route table") - err = c.routetables.DeleteAndWait(ctx, vnetResourceGroup, clusterName+"-rt") - if err != nil { - errs = append(errs, err) - } + switch { + case c.ci && env.IsLocalDevelopmentMode(): // PR E2E + errs = append(errs, + c.deleteCluster(ctx, vnetResourceGroup, clusterName), + c.deleteClusterResourceGroup(ctx, vnetResourceGroup), + c.deleteVnetPeerings(ctx, vnetResourceGroup), + ) + case c.ci: // Prod E2E + errs = append(errs, c.deleteClusterResourceGroup(ctx, vnetResourceGroup)) + default: + errs = append(errs, + c.deleteRoleAssignments(ctx, vnetResourceGroup, clusterName), + c.deleteCluster(ctx, vnetResourceGroup, clusterName), + c.deleteDeployment(ctx, vnetResourceGroup, clusterName), // Deleting the deployment does not clean up the associated resources + c.deleteVnetResources(ctx, vnetResourceGroup, "dev-vnet", clusterName), + ) } c.log.Info("done") - - if errs != nil { - return errs // https://golang.org/doc/faq#nil_error - } - - return nil + return errors.Join(errs...) } // createCluster created new clusters, based on where it is running. @@ -647,10 +603,15 @@ func (c *Cluster) fixupNSGs(ctx context.Context, vnetResourceGroup, clusterName return nil } -func (c *Cluster) deleteRoleAssignments(ctx context.Context, vnetResourceGroup, appID string) error { - spObjID, err := utilgraph.GetServicePrincipalIDByAppID(ctx, c.spGraphClient, appID) +func (c *Cluster) deleteRoleAssignments(ctx context.Context, vnetResourceGroup, clusterName string) error { + c.log.Print("deleting role assignments") + oc, err := c.openshiftclustersv20200430.Get(ctx, vnetResourceGroup, clusterName) if err != nil { - return err + return fmt.Errorf("error getting cluster document: %w", err) + } + spObjID, err := utilgraph.GetServicePrincipalIDByAppID(ctx, c.spGraphClient, *oc.OpenShiftClusterProperties.ServicePrincipalProfile.ClientID) + if err != nil { + return fmt.Errorf("error getting service principal for cluster: %w", err) } if spObjID == nil { return nil @@ -658,7 +619,7 @@ func (c *Cluster) deleteRoleAssignments(ctx context.Context, vnetResourceGroup, roleAssignments, err := c.roleassignments.ListForResourceGroup(ctx, vnetResourceGroup, fmt.Sprintf("principalId eq '%s'", *spObjID)) if err != nil { - return err + return fmt.Errorf("error listing role assignments for service principal: %w", err) } for _, roleAssignment := range roleAssignments { @@ -670,7 +631,7 @@ func (c *Cluster) deleteRoleAssignments(ctx context.Context, vnetResourceGroup, c.log.Infof("deleting role assignment %s", *roleAssignment.Name) _, err = c.roleassignments.Delete(ctx, *roleAssignment.Scope, *roleAssignment.Name) if err != nil { - return err + return fmt.Errorf("error deleting role assignment %s: %w", *roleAssignment.Name, err) } } } @@ -678,6 +639,71 @@ func (c *Cluster) deleteRoleAssignments(ctx context.Context, vnetResourceGroup, return nil } +func (c *Cluster) deleteCluster(ctx context.Context, resourceGroup, clusterName string) error { + c.log.Printf("deleting cluster %s", clusterName) + if err := c.openshiftclustersv20200430.DeleteAndWait(ctx, resourceGroup, clusterName); err != nil { + return fmt.Errorf("error deleting cluster %s: %w", clusterName, err) + } + return nil +} + +func (c *Cluster) deleteClusterResourceGroup(ctx context.Context, resourceGroup string) error { + if _, err := c.groups.Get(ctx, resourceGroup); err != nil { + c.log.Printf("error getting resource group %s, skipping deletion: %v", resourceGroup, err) + return nil + } + + c.log.Printf("deleting resource group %s", resourceGroup) + if err := c.groups.DeleteAndWait(ctx, resourceGroup); err != nil { + return fmt.Errorf("error deleting resource group: %w", err) + } + + return nil +} + +func (c *Cluster) deleteVnetPeerings(ctx context.Context, resourceGroup string) error { + r, err := azure.ParseResourceID(c.ciParentVnet) + if err == nil { + err = c.ciParentVnetPeerings.DeleteAndWait(ctx, r.ResourceGroup, r.ResourceName, resourceGroup+"-peer") + } + if err != nil { + return fmt.Errorf("error deleting vnet peerings: %w", err) + } + + return nil +} + +func (c *Cluster) deleteDeployment(ctx context.Context, resourceGroup, clusterName string) error { + c.log.Info("deleting deployment") + if err := c.deployments.DeleteAndWait(ctx, resourceGroup, clusterName); err != nil { + return fmt.Errorf("error deleting deployment: %w", err) + } + return nil +} + +func (c *Cluster) deleteVnetResources(ctx context.Context, resourceGroup, vnetName, clusterName string) error { + var errs []error + + c.log.Info("deleting master/worker subnets") + if err := c.subnets.DeleteAndWait(ctx, resourceGroup, vnetName, clusterName+"-master"); err != nil { + c.log.Errorf("error when deleting master subnet: %v", err) + errs = append(errs, err) + } + + if err := c.subnets.DeleteAndWait(ctx, resourceGroup, vnetName, clusterName+"-worker"); err != nil { + c.log.Errorf("error when deleting worker subnet: %v", err) + errs = append(errs, err) + } + + c.log.Info("deleting route table") + if err := c.routetables.DeleteAndWait(ctx, resourceGroup, clusterName+"-rt"); err != nil { + c.log.Errorf("error when deleting route table: %v", err) + errs = append(errs, err) + } + + return errors.Join(errs...) +} + func (c *Cluster) peerSubnetsToCI(ctx context.Context, vnetResourceGroup, clusterName string) error { cluster := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/dev-vnet", c.env.SubscriptionID(), vnetResourceGroup) diff --git a/pkg/util/mocks/azureclient/mgmt/authorization/authorization.go b/pkg/util/mocks/azureclient/mgmt/authorization/authorization.go index ecab9c846..61f0e9ac8 100644 --- a/pkg/util/mocks/azureclient/mgmt/authorization/authorization.go +++ b/pkg/util/mocks/azureclient/mgmt/authorization/authorization.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization (interfaces: PermissionsClient,RoleAssignmentsClient,DenyAssignmentClient,RoleDefinitionsClient) +// Source: github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization (interfaces: RoleAssignmentsClient,DenyAssignmentClient,RoleDefinitionsClient) // Package mock_authorization is a generated GoMock package. package mock_authorization @@ -12,59 +12,6 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockPermissionsClient is a mock of PermissionsClient interface. -type MockPermissionsClient struct { - ctrl *gomock.Controller - recorder *MockPermissionsClientMockRecorder -} - -// MockPermissionsClientMockRecorder is the mock recorder for MockPermissionsClient. -type MockPermissionsClientMockRecorder struct { - mock *MockPermissionsClient -} - -// NewMockPermissionsClient creates a new mock instance. -func NewMockPermissionsClient(ctrl *gomock.Controller) *MockPermissionsClient { - mock := &MockPermissionsClient{ctrl: ctrl} - mock.recorder = &MockPermissionsClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPermissionsClient) EXPECT() *MockPermissionsClientMockRecorder { - return m.recorder -} - -// ListForResource mocks base method. -func (m *MockPermissionsClient) ListForResource(arg0 context.Context, arg1, arg2, arg3, arg4, arg5 string) ([]authorization.Permission, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListForResource", arg0, arg1, arg2, arg3, arg4, arg5) - ret0, _ := ret[0].([]authorization.Permission) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListForResource indicates an expected call of ListForResource. -func (mr *MockPermissionsClientMockRecorder) ListForResource(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListForResource", reflect.TypeOf((*MockPermissionsClient)(nil).ListForResource), arg0, arg1, arg2, arg3, arg4, arg5) -} - -// ListForResourceGroup mocks base method. -func (m *MockPermissionsClient) ListForResourceGroup(arg0 context.Context, arg1 string) ([]authorization.Permission, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListForResourceGroup", arg0, arg1) - ret0, _ := ret[0].([]authorization.Permission) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListForResourceGroup indicates an expected call of ListForResourceGroup. -func (mr *MockPermissionsClientMockRecorder) ListForResourceGroup(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListForResourceGroup", reflect.TypeOf((*MockPermissionsClient)(nil).ListForResourceGroup), arg0, arg1) -} - // MockRoleAssignmentsClient is a mock of RoleAssignmentsClient interface. type MockRoleAssignmentsClient struct { ctrl *gomock.Controller diff --git a/pkg/util/permissions/permissions.go b/pkg/util/permissions/permissions.go deleted file mode 100644 index 29e3b72c4..000000000 --- a/pkg/util/permissions/permissions.go +++ /dev/null @@ -1,52 +0,0 @@ -package permissions - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "regexp" - "strings" - - mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" -) - -// CanDoAction returns true if a given action is granted by a set of permissions -func CanDoAction(ps []mgmtauthorization.Permission, a string) (bool, error) { - for _, p := range ps { - var matched bool - - for _, action := range *p.Actions { - action := regexp.QuoteMeta(action) - action = "(?i)^" + strings.ReplaceAll(action, `\*`, ".*") + "$" - rx, err := regexp.Compile(action) - if err != nil { - return false, err - } - if rx.MatchString(a) { - matched = true - break - } - } - if !matched { - continue - } - - for _, notAction := range *p.NotActions { - notAction := regexp.QuoteMeta(notAction) - notAction = "(?i)^" + strings.ReplaceAll(notAction, `\*`, ".*") + "$" - rx, err := regexp.Compile(notAction) - if err != nil { - return false, err - } - if rx.MatchString(a) { - matched = false - break - } - } - if matched { - return true, nil - } - } - - return false, nil -} diff --git a/pkg/util/permissions/permissions_test.go b/pkg/util/permissions/permissions_test.go deleted file mode 100644 index 6a5ef2682..000000000 --- a/pkg/util/permissions/permissions_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package permissions - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "testing" - - mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" -) - -func TestCanDoAction(t *testing.T) { - tests := []struct { - name string - permissions []mgmtauthorization.Permission - action string - want bool - }{ - { - name: "empty permissions list", - action: "Microsoft.Network/virtualNetworks/subnets/join/action", - }, - { - name: "has permission - exact", - permissions: []mgmtauthorization.Permission{ - { - Actions: &[]string{"Microsoft.Compute/virtualMachines/*"}, - NotActions: &[]string{}, - }, - { - Actions: &[]string{"Microsoft.Network/virtualNetworks/subnets/join/action"}, - NotActions: &[]string{}, - }, - }, - action: "Microsoft.Network/virtualNetworks/subnets/join/action", - want: true, - }, - { - name: "has permission - wildcard", - permissions: []mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Network/virtualNetworks/subnets/*/action"}, - NotActions: &[]string{}, - }}, - action: "Microsoft.Network/virtualNetworks/subnets/join/action", - want: true, - }, - { - name: "has permission - exact, conflict", - permissions: []mgmtauthorization.Permission{ - { - Actions: &[]string{"Microsoft.Network/virtualNetworks/subnets/join/action"}, - NotActions: &[]string{}, - }, - { - Actions: &[]string{}, - NotActions: &[]string{"Microsoft.Network/virtualNetworks/subnets/join/action"}, - }, - }, - action: "Microsoft.Network/virtualNetworks/subnets/join/action", - want: true, - }, - { - name: "has permission excluded - exact", - permissions: []mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Network/*"}, - NotActions: &[]string{"Microsoft.Network/virtualNetworks/subnets/join/action"}, - }}, - action: "Microsoft.Network/virtualNetworks/subnets/join/action", - }, - { - name: "has permission excluded - wildcard", - permissions: []mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Network/*"}, - NotActions: &[]string{"Microsoft.Network/virtualNetworks/subnets/*/action"}, - }}, - action: "Microsoft.Network/virtualNetworks/subnets/join/action", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ok, err := CanDoAction(test.permissions, test.action) - if err != nil { - t.Fatalf("unexpected error: %#v", err) - } - - if ok != test.want { - t.Errorf("expected result %#v, got %#v", test.want, ok) - } - }) - } -} diff --git a/pkg/validate/dynamic/diskencryptionset_test.go b/pkg/validate/dynamic/diskencryptionset_test.go index 3b684ff21..da208e83f 100644 --- a/pkg/validate/dynamic/diskencryptionset_test.go +++ b/pkg/validate/dynamic/diskencryptionset_test.go @@ -11,7 +11,6 @@ import ( "testing" mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" - mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" @@ -19,7 +18,10 @@ import ( "github.com/sirupsen/logrus" "github.com/Azure/ARO-RP/pkg/api" - mock_authorization "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/authorization" + "github.com/Azure/ARO-RP/pkg/util/azureclient" + "github.com/Azure/ARO-RP/pkg/util/azureclient/authz/remotepdp" + mock_remotepdp "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/authz/remotepdp" + mock_azcore "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/azuresdk/azcore" mock_compute "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/compute" utilerror "github.com/Azure/ARO-RP/test/util/error" ) @@ -44,10 +46,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { t.Run(string(authorizerType), func(t *testing.T) { for _, tt := range []struct { - name string - oc *api.OpenShiftCluster - mocks func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) - wantErr string + name string + oc *api.OpenShiftCluster + actionInfos []remotepdp.ActionInfo + mocks func(*mock_compute.MockDiskEncryptionSetsClient, *mock_remotepdp.MockRemotePDPClient, *mock_azcore.MockTokenCredential, context.CancelFunc) + wantErr string }{ { name: "no disk encryption set provided", @@ -66,13 +69,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + Return(validDiskEncryptionAuthorizationDecision, nil) diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{Location: to.StringPtr("eastus")}, nil) @@ -91,13 +92,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + Return(validDiskEncryptionAuthorizationDecision, nil) diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{Location: to.StringPtr("eastus")}, nil) @@ -116,19 +115,21 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR2.ResourceGroup, fakeDesR2.Provider, "", fakeDesR2.ResourceType, fakeDesR2.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, authReq remotepdp.AuthorizationRequest) (*remotepdp.AuthorizationDecisionResponse, error) { + cancel() // wait.PollImmediateUntil will always be invoked at least once + switch authReq.Resource.Id { + case fakeDesR1.String(): + return validDiskEncryptionAuthorizationDecision, nil + case fakeDesR2.String(): + return validDiskEncryptionAuthorizationDecision, nil + } + return invalidDiskEncryptionAuthorizationDecisionsReadNotAllowed, nil + }, + ).AnyTimes() diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{Location: to.StringPtr("eastus")}, nil) @@ -150,13 +151,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + Return(validDiskEncryptionAuthorizationDecision, nil) diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{}, autorest.DetailedError{StatusCode: http.StatusNotFound}) @@ -176,9 +175,10 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). Return(nil, errors.New("fakeerr")) }, wantErr: "fakeerr", @@ -196,13 +196,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + Return(validDiskEncryptionAuthorizationDecision, nil) diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{}, errors.New("fakeerr")) @@ -222,10 +220,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) { + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + Do(func(arg0, arg1 interface{}) { cancel() }) }, @@ -244,16 +243,21 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR2.ResourceGroup, fakeDesR2.Provider, "", fakeDesR2.ResourceType, fakeDesR2.ResourceName). - Return(nil, autorest.DetailedError{StatusCode: http.StatusNotFound}) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, authReq remotepdp.AuthorizationRequest) (*remotepdp.AuthorizationDecisionResponse, error) { + cancel() // wait.PollImmediateUntil will always be invoked at least once + switch authReq.Resource.Id { + case fakeDesR1.String(): + return validDiskEncryptionAuthorizationDecision, nil + case fakeDesR2.String(): + return nil, autorest.DetailedError{StatusCode: http.StatusNotFound} + } + return invalidDiskEncryptionAuthorizationDecisionsReadNotAllowed, nil + }, + ).AnyTimes() diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{Location: to.StringPtr("eastus")}, nil) @@ -273,13 +277,11 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }}, }, }, - mocks: func(permissions *mock_authorization.MockPermissionsClient, diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, cancel context.CancelFunc) { - permissions.EXPECT(). - ListForResource(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.Provider, "", fakeDesR1.ResourceType, fakeDesR1.ResourceName). - Return([]mgmtauthorization.Permission{{ - Actions: &[]string{"Microsoft.Compute/diskEncryptionSets/read"}, - NotActions: &[]string{}, - }}, nil) + mocks: func(diskEncryptionSets *mock_compute.MockDiskEncryptionSetsClient, pdpClient *mock_remotepdp.MockRemotePDPClient, tokenCred *mock_azcore.MockTokenCredential, cancel context.CancelFunc) { + mockTokenCredential(tokenCred) + pdpClient.EXPECT(). + CheckAccess(gomock.Any(), gomock.Any()). + Return(validDiskEncryptionAuthorizationDecision, nil) diskEncryptionSets.EXPECT(). Get(gomock.Any(), fakeDesR1.ResourceGroup, fakeDesR1.ResourceName). Return(mgmtcompute.DiskEncryptionSet{Location: to.StringPtr("westeurope")}, nil) @@ -294,18 +296,21 @@ func TestValidateDiskEncryptionSets(t *testing.T) { controller := gomock.NewController(t) defer controller.Finish() - permissionsClient := mock_authorization.NewMockPermissionsClient(controller) diskEncryptionSetsClient := mock_compute.NewMockDiskEncryptionSetsClient(controller) + tokenCred := mock_azcore.NewMockTokenCredential(controller) + remotePDPClient := mock_remotepdp.NewMockRemotePDPClient(controller) if tt.mocks != nil { - tt.mocks(permissionsClient, diskEncryptionSetsClient, cancel) + tt.mocks(diskEncryptionSetsClient, remotePDPClient, tokenCred, cancel) } dv := &dynamic{ - authorizerType: authorizerType, - log: logrus.NewEntry(logrus.StandardLogger()), - permissions: permissionsClient, - diskEncryptionSets: diskEncryptionSetsClient, + azEnv: &azureclient.PublicCloud, + authorizerType: authorizerType, + log: logrus.NewEntry(logrus.StandardLogger()), + diskEncryptionSets: diskEncryptionSetsClient, + pdpClient: remotePDPClient, + checkAccessSubjectInfoCred: tokenCred, } err := dv.ValidateDiskEncryptionSets(ctx, tt.oc) @@ -315,3 +320,22 @@ func TestValidateDiskEncryptionSets(t *testing.T) { }) } } + +var ( + invalidDiskEncryptionAuthorizationDecisionsReadNotAllowed = &remotepdp.AuthorizationDecisionResponse{ + Value: []remotepdp.AuthorizationDecision{ + { + ActionId: "Microsoft.Compute/diskEncryptionSets/read", + AccessDecision: remotepdp.NotAllowed, + }, + }, + } + validDiskEncryptionAuthorizationDecision = &remotepdp.AuthorizationDecisionResponse{ + Value: []remotepdp.AuthorizationDecision{ + { + ActionId: "Microsoft.Compute/diskEncryptionSets/read", + AccessDecision: remotepdp.Allowed, + }, + }, + } +) diff --git a/pkg/validate/dynamic/dynamic.go b/pkg/validate/dynamic/dynamic.go index b8d28671d..ba87cfa16 100644 --- a/pkg/validate/dynamic/dynamic.go +++ b/pkg/validate/dynamic/dynamic.go @@ -25,11 +25,8 @@ import ( "github.com/Azure/ARO-RP/pkg/env" "github.com/Azure/ARO-RP/pkg/util/azureclient" "github.com/Azure/ARO-RP/pkg/util/azureclient/authz/remotepdp" - "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network" - "github.com/Azure/ARO-RP/pkg/util/permissions" - "github.com/Azure/ARO-RP/pkg/util/steps" "github.com/Azure/ARO-RP/pkg/util/token" ) @@ -87,7 +84,6 @@ type dynamic struct { env env.Interface azEnv *azureclient.AROEnvironment - permissions authorization.PermissionsClient virtualNetworks virtualNetworksGetClient diskEncryptionSets compute.DiskEncryptionSetsClient resourceSkusClient compute.ResourceSkusClient @@ -125,7 +121,6 @@ func NewValidator( spComputeUsage: compute.NewUsageClient(azEnv, subscriptionID, authorizer), spNetworkUsage: network.NewUsageClient(azEnv, subscriptionID, authorizer), - permissions: authorization.NewPermissionsClient(azEnv, subscriptionID, authorizer), virtualNetworks: newVirtualNetworksCache( network.NewVirtualNetworksClient(azEnv, subscriptionID, authorizer), ), @@ -397,12 +392,8 @@ func (dv *dynamic) validateActions(ctx context.Context, r *azure.Resource, actio defer cancel() c := closure{dv: dv, ctx: ctx, resource: r, actions: actions} - conditionalFunc := c.usingListPermissions - if dv.pdpClient != nil { - conditionalFunc = c.usingCheckAccessV2 - } - return wait.PollImmediateUntil(30*time.Second, conditionalFunc, timeoutCtx.Done()) + return wait.PollImmediateUntil(30*time.Second, c.usingCheckAccessV2, timeoutCtx.Done()) } // closure is the closure used in PollImmediateUntil's ConditionalFunc @@ -414,47 +405,8 @@ type closure struct { oid *string } -// usingListPermissions is how the current check is done -func (c closure) usingListPermissions() (bool, error) { - c.dv.log.Debug("retry validateActions with ListPermissions") - perms, err := c.dv.permissions.ListForResource( - c.ctx, - c.resource.ResourceGroup, - c.resource.Provider, - "", - c.resource.ResourceType, - c.resource.ResourceName, - ) - if err != nil { - return false, err - } - - // If we get a StatusForbidden, try refreshing the SP (since with a - // brand-new SP it might take time to propagate) - if detailedErr, ok := err.(autorest.DetailedError); ok && - detailedErr.StatusCode == http.StatusForbidden { - return false, steps.ErrWantRefresh - } - if err != nil { - return false, err - } - - for _, action := range c.actions { - ok, err := permissions.CanDoAction(perms, action) - if !ok || err != nil { - // TODO(jminter): I don't understand if there are genuinely - // cases where CanDoAction can return false then true shortly - // after. I'm a little skeptical; if it can't happen we can - // simplify this code. We should add a metric on this. - return false, err - } - } - return true, nil -} - // usingCheckAccessV2 uses the new RBAC checkAccessV2 API func (c closure) usingCheckAccessV2() (bool, error) { - // TODO remove this when fully migrated to CheckAccess c.dv.log.Info("validateActions with CheckAccessV2") // reusing oid during retries diff --git a/pkg/validate/dynamic/dynamic_test.go b/pkg/validate/dynamic/dynamic_test.go index 50e9911f8..540b9a264 100644 --- a/pkg/validate/dynamic/dynamic_test.go +++ b/pkg/validate/dynamic/dynamic_test.go @@ -7,14 +7,11 @@ import ( "context" "errors" "fmt" - "net/http" "testing" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" - mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization" - "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "github.com/golang/mock/gomock" @@ -25,7 +22,6 @@ import ( "github.com/Azure/ARO-RP/pkg/util/azureclient/authz/remotepdp" mock_remotepdp "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/authz/remotepdp" mock_azcore "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/azuresdk/azcore" - mock_authorization "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/authorization" mock_network "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/network" utilerror "github.com/Azure/ARO-RP/test/util/error" ) @@ -48,122 +44,6 @@ var ( workerNSGv1 = resourceGroupID + "/providers/Microsoft.Network/networkSecurityGroups/aro-node-nsg" ) -func TestValidateVnetPermissions(t *testing.T) { - ctx := context.Background() - - resourceType := "virtualNetworks" - resourceProvider := "Microsoft.Network" - - for _, tt := range []struct { - name string - mocks func(*mock_authorization.MockPermissionsClient, context.CancelFunc) - wantErr string - }{ - { - name: "pass", - mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName). - Return([]mgmtauthorization.Permission{ - { - Actions: &[]string{ - "Microsoft.Network/virtualNetworks/join/action", - "Microsoft.Network/virtualNetworks/read", - "Microsoft.Network/virtualNetworks/write", - "Microsoft.Network/virtualNetworks/subnets/join/action", - "Microsoft.Network/virtualNetworks/subnets/read", - "Microsoft.Network/virtualNetworks/subnets/write", - }, - NotActions: &[]string{}, - }, - }, nil) - }, - }, - { - name: "fail: missing permissions", - mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName). - Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) { - cancel() - }). - Return( - []mgmtauthorization.Permission{ - { - Actions: &[]string{}, - NotActions: &[]string{}, - }, - }, - nil, - ) - }, - wantErr: "400: InvalidServicePrincipalPermissions: : The cluster service principal (Application ID: fff51942-b1f9-4119-9453-aaa922259eb7) does not have Network Contributor role on vnet '" + vnetID + "'.", - }, - { - name: "fail: not found", - mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName). - Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) { - cancel() - }). - Return( - nil, - autorest.DetailedError{ - StatusCode: http.StatusNotFound, - }, - ) - }, - wantErr: "400: InvalidLinkedVNet: : The vnet '" + vnetID + "' could not be found.", - }, - { - name: "fail: fp/sp has no permission on the target vnet (forbidden error)", - mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName). - Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) { - cancel() - }). - Return( - nil, - autorest.DetailedError{ - StatusCode: http.StatusForbidden, - Message: "some forbidden error on the resource.", - }, - ) - }, - - wantErr: "400: InvalidServicePrincipalPermissions: : The cluster service principal (Application ID: fff51942-b1f9-4119-9453-aaa922259eb7) does not have Network Contributor role on vnet '" + vnetID + "'.\nOriginal error message: some forbidden error on the resource.", - }, - } { - t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - permissionsClient := mock_authorization.NewMockPermissionsClient(controller) - tt.mocks(permissionsClient, cancel) - - dv := &dynamic{ - appID: "fff51942-b1f9-4119-9453-aaa922259eb7", - authorizerType: AuthorizerClusterServicePrincipal, - log: logrus.NewEntry(logrus.StandardLogger()), - permissions: permissionsClient, - } - - vnetr, err := azure.ParseResourceID(vnetID) - if err != nil { - t.Fatal(err) - } - - err = dv.validateVnetPermissions(ctx, vnetr) - utilerror.AssertErrorMessage(t, err, tt.wantErr) - }) - } -} - func TestGetRouteTableID(t *testing.T) { for _, tt := range []struct { name string @@ -272,324 +152,6 @@ func TestGetNatGatewayID(t *testing.T) { } } -func TestValidateRouteTablesPermissions(t *testing.T) { - ctx := context.Background() - - for _, tt := range []struct { - name string - subnet Subnet - permissionMocks func(*mock_authorization.MockPermissionsClient, context.CancelFunc) - vnetMocks func(*mock_network.MockVirtualNetworksClient, mgmtnetwork.VirtualNetwork) - wantErr string - }{ - { - name: "fail: failed to get vnet", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, errors.New("failed to get vnet")) - }, - wantErr: "failed to get vnet", - }, - { - name: "fail: master subnet doesn't exist", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnet.Subnets = nil - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + masterSubnet + "' could not be found.", - }, - { - name: "fail: worker subnet ID doesn't exist", - subnet: Subnet{ID: workerSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - (*vnet.Subnets)[1].ID = to.StringPtr("not valid") - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + workerSubnet + "' could not be found.", - }, - { - name: "fail: permissions don't exist", - subnet: Subnet{ID: workerSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - permissionMocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "routeTables", gomock.Any()). - Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) { - cancel() - }). - Return( - []mgmtauthorization.Permission{ - { - Actions: &[]string{}, - NotActions: &[]string{}, - }, - }, - nil, - ) - }, - wantErr: "400: InvalidServicePrincipalPermissions: : The cluster service principal does not have Network Contributor role on route table '" + workerRtID + "'.", - }, - { - name: "pass", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - permissionMocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "routeTables", gomock.Any()). - AnyTimes(). - Return([]mgmtauthorization.Permission{ - { - Actions: &[]string{ - "Microsoft.Network/routeTables/join/action", - "Microsoft.Network/routeTables/read", - "Microsoft.Network/routeTables/write", - }, - NotActions: &[]string{}, - }, - }, nil) - }, - }, - { - name: "pass: no route table to check", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - (*vnet.Subnets)[0].RouteTable = nil - (*vnet.Subnets)[1].RouteTable = nil - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - permissionsClient := mock_authorization.NewMockPermissionsClient(controller) - vnetClient := mock_network.NewMockVirtualNetworksClient(controller) - - vnet := &mgmtnetwork.VirtualNetwork{ - ID: &vnetID, - VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{ - Subnets: &[]mgmtnetwork.Subnet{ - { - ID: &masterSubnet, - SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{ - RouteTable: &mgmtnetwork.RouteTable{ - ID: &masterRtID, - }, - }, - }, - { - ID: &workerSubnet, - SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{ - RouteTable: &mgmtnetwork.RouteTable{ - ID: &workerRtID, - }, - }, - }, - }, - }, - } - - dv := &dynamic{ - authorizerType: AuthorizerClusterServicePrincipal, - log: logrus.NewEntry(logrus.StandardLogger()), - permissions: permissionsClient, - virtualNetworks: vnetClient, - } - - if tt.permissionMocks != nil { - tt.permissionMocks(permissionsClient, cancel) - } - - if tt.vnetMocks != nil { - tt.vnetMocks(vnetClient, *vnet) - } - - err := dv.validateRouteTablePermissions(ctx, tt.subnet) - utilerror.AssertErrorMessage(t, err, tt.wantErr) - }) - } -} - -func TestValidateNatGatewaysPermissions(t *testing.T) { - ctx := context.Background() - - for _, tt := range []struct { - name string - subnet Subnet - permissionMocks func(*mock_authorization.MockPermissionsClient, context.CancelFunc) - vnetMocks func(*mock_network.MockVirtualNetworksClient, mgmtnetwork.VirtualNetwork) - wantErr string - }{ - { - name: "fail: failed to get vnet", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, errors.New("failed to get vnet")) - }, - wantErr: "failed to get vnet", - }, - { - name: "fail: master subnet doesn't exist", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnet.Subnets = nil - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + masterSubnet + "' could not be found.", - }, - { - name: "fail: worker subnet ID doesn't exist", - subnet: Subnet{ID: workerSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - (*vnet.Subnets)[1].ID = to.StringPtr("not valid") - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + workerSubnet + "' could not be found.", - }, - { - name: "fail: permissions don't exist", - subnet: Subnet{ID: workerSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - permissionMocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "natGateways", gomock.Any()). - Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) { - cancel() - }). - Return( - []mgmtauthorization.Permission{ - { - Actions: &[]string{}, - NotActions: &[]string{}, - }, - }, - nil, - ) - }, - wantErr: "400: InvalidServicePrincipalPermissions: : The cluster service principal does not have Network Contributor role on nat gateway '" + workerNgID + "'.", - }, - { - name: "pass", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - permissionMocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel context.CancelFunc) { - permissionsClient.EXPECT(). - ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "natGateways", gomock.Any()). - AnyTimes(). - Return([]mgmtauthorization.Permission{ - { - Actions: &[]string{ - "Microsoft.Network/natGateways/join/action", - "Microsoft.Network/natGateways/read", - "Microsoft.Network/natGateways/write", - }, - NotActions: &[]string{}, - }, - }, nil) - }, - }, - { - name: "pass: no nat gateway to check", - subnet: Subnet{ID: masterSubnet}, - vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) { - (*vnet.Subnets)[0].NatGateway = nil - (*vnet.Subnets)[1].NatGateway = nil - vnetClient.EXPECT(). - Get(gomock.Any(), resourceGroupName, vnetName, ""). - Return(vnet, nil) - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - permissionsClient := mock_authorization.NewMockPermissionsClient(controller) - vnetClient := mock_network.NewMockVirtualNetworksClient(controller) - - vnet := &mgmtnetwork.VirtualNetwork{ - ID: &vnetID, - VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{ - Subnets: &[]mgmtnetwork.Subnet{ - { - ID: &masterSubnet, - SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{ - NatGateway: &mgmtnetwork.SubResource{ - ID: &masterNgID, - }, - }, - }, - { - ID: &workerSubnet, - SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{ - NatGateway: &mgmtnetwork.SubResource{ - ID: &workerNgID, - }, - }, - }, - }, - }, - } - - dv := &dynamic{ - authorizerType: AuthorizerClusterServicePrincipal, - log: logrus.NewEntry(logrus.StandardLogger()), - permissions: permissionsClient, - virtualNetworks: vnetClient, - } - - if tt.permissionMocks != nil { - tt.permissionMocks(permissionsClient, cancel) - } - - if tt.vnetMocks != nil { - tt.vnetMocks(vnetClient, *vnet) - } - - err := dv.validateNatGatewayPermissions(ctx, tt.subnet) - utilerror.AssertErrorMessage(t, err, tt.wantErr) - }) - } -} - func TestValidateCIDRRanges(t *testing.T) { ctx := context.Background() @@ -1131,7 +693,7 @@ func mockTokenCredential(tokenCred *mock_azcore.MockTokenCredential) { Return(mockAccessToken, nil) } -func TestValidateVnetPermissionsWithCheckAccess(t *testing.T) { +func TestValidateVnetPermissions(t *testing.T) { ctx := context.Background() for _, tt := range []struct { @@ -1289,7 +851,7 @@ var ( } ) -func TestValidateRouteTablesPermissionsWithCheckAccess(t *testing.T) { +func TestValidateRouteTablesPermissions(t *testing.T) { ctx := context.Background() for _, tt := range []struct { @@ -1504,7 +1066,7 @@ var ( } ) -func TestValidateNatGatewaysPermissionsWithCheckAccess(t *testing.T) { +func TestValidateNatGatewaysPermissions(t *testing.T) { ctx := context.Background() for _, tt := range []struct { diff --git a/pkg/validate/openshiftcluster_validatedynamic.go b/pkg/validate/openshiftcluster_validatedynamic.go index 46fccef48..0e878caf0 100644 --- a/pkg/validate/openshiftcluster_validatedynamic.go +++ b/pkg/validate/openshiftcluster_validatedynamic.go @@ -19,7 +19,6 @@ import ( "github.com/Azure/ARO-RP/pkg/api" "github.com/Azure/ARO-RP/pkg/env" "github.com/Azure/ARO-RP/pkg/util/azureclient/authz/remotepdp" - "github.com/Azure/ARO-RP/pkg/util/feature" "github.com/Azure/ARO-RP/pkg/validate/dynamic" ) @@ -134,20 +133,12 @@ func (dv *openShiftClusterDynamicValidator) Dynamic(ctx context.Context) error { return err } - if useCheckAccess || feature.IsRegisteredForFeature( - dv.subscriptionDoc.Subscription.Properties, - api.FeatureFlagCheckAccessTestToggle, - ) { - // TODO remove after successfully migrating to CheckAccess - dv.log.Info("Using CheckAccess instead of ListPermissions") - - aroEnv := dv.env.Environment() - pdpClient = remotepdp.NewRemotePDPClient( - fmt.Sprintf(aroEnv.Endpoint, dv.env.Location()), - aroEnv.OAuthScope, - fpClientCred, - ) - } + aroEnv := dv.env.Environment() + pdpClient = remotepdp.NewRemotePDPClient( + fmt.Sprintf(aroEnv.Endpoint, dv.env.Location()), + aroEnv.OAuthScope, + fpClientCred, + ) scopes := []string{dv.env.Environment().ResourceManagerScope} err = ensureAccessTokenClaims(ctx, spClientCred, scopes) diff --git a/test/util/clienthelper/hookclient.go b/test/util/clienthelper/hookclient.go index 49b988adf..767e6c90d 100644 --- a/test/util/clienthelper/hookclient.go +++ b/test/util/clienthelper/hookclient.go @@ -18,59 +18,110 @@ type hookFunc func(obj client.Object) error type HookingClient struct { f client.WithWatch - getHook []getFunc - deleteHook []hookFunc - createHook []hookFunc - updateHook []hookFunc - patchHook []hookFunc + preGetHook []getFunc + preDeleteHook []hookFunc + preCreateHook []hookFunc + preUpdateHook []hookFunc + prePatchHook []hookFunc + + postGetHook []getFunc + postDeleteHook []hookFunc + postCreateHook []hookFunc + postUpdateHook []hookFunc + postPatchHook []hookFunc } var _ client.Client = &HookingClient{} -// NewHookingClient creates a +// NewHookingClient creates a client which allows hooks to be added before and +// after the client event. Errors returned in the hooks are returned to the +// caller directly, to simulate issues such as disconnections or errors. +// Prehooks that cause errors will cause the underlying wrapped client to not be +// called. func NewHookingClient(c client.WithWatch) *HookingClient { return &HookingClient{ - f: c, - getHook: []getFunc{}, - deleteHook: []hookFunc{}, - createHook: []hookFunc{}, - updateHook: []hookFunc{}, - patchHook: []hookFunc{}, + f: c, + preGetHook: []getFunc{}, + preDeleteHook: []hookFunc{}, + preCreateHook: []hookFunc{}, + preUpdateHook: []hookFunc{}, + prePatchHook: []hookFunc{}, + + postGetHook: []getFunc{}, + postDeleteHook: []hookFunc{}, + postCreateHook: []hookFunc{}, + postUpdateHook: []hookFunc{}, + postPatchHook: []hookFunc{}, } } func (c *HookingClient) WithGetHook(f getFunc) *HookingClient { - c.getHook = append(c.getHook, f) + c.postGetHook = append(c.postGetHook, f) return c } func (c *HookingClient) WithDeleteHook(f hookFunc) *HookingClient { - c.deleteHook = append(c.deleteHook, f) + c.postDeleteHook = append(c.postDeleteHook, f) return c } func (c *HookingClient) WithCreateHook(f hookFunc) *HookingClient { - c.createHook = append(c.createHook, f) + c.postCreateHook = append(c.postCreateHook, f) return c } func (c *HookingClient) WithUpdateHook(f hookFunc) *HookingClient { - c.updateHook = append(c.updateHook, f) + c.postUpdateHook = append(c.postUpdateHook, f) return c } func (c *HookingClient) WithPatchHook(f hookFunc) *HookingClient { - c.patchHook = append(c.patchHook, f) + c.postPatchHook = append(c.postPatchHook, f) + return c +} + +func (c *HookingClient) WithPreGetHook(f getFunc) *HookingClient { + c.preGetHook = append(c.preGetHook, f) + return c +} + +func (c *HookingClient) WithPreDeleteHook(f hookFunc) *HookingClient { + c.preDeleteHook = append(c.preDeleteHook, f) + return c +} + +func (c *HookingClient) WithPreCreateHook(f hookFunc) *HookingClient { + c.preCreateHook = append(c.preCreateHook, f) + return c +} +func (c *HookingClient) WithPreUpdateHook(f hookFunc) *HookingClient { + c.preUpdateHook = append(c.preUpdateHook, f) + return c +} +func (c *HookingClient) WithPrePatchHook(f hookFunc) *HookingClient { + c.prePatchHook = append(c.prePatchHook, f) return c } // See [sigs.k8s.io/controller-runtime/pkg/client.Reader.Get] func (c *HookingClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - for _, h := range c.getHook { + for _, h := range c.preGetHook { err := h(key, obj) if err != nil { return err } } - return c.f.Get(ctx, key, obj) + + err := c.f.Get(ctx, key, obj) + if err != nil { + return err + } + + for _, h := range c.postGetHook { + err := h(key, obj) + if err != nil { + return err + } + } + return nil } // See [sigs.k8s.io/controller-runtime/pkg/client.Reader.List] @@ -85,24 +136,48 @@ func (c *HookingClient) Watch(ctx context.Context, list client.ObjectList, opts // See [sigs.k8s.io/controller-runtime/pkg/client.Writer.Create] func (c *HookingClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - for _, h := range c.createHook { + for _, h := range c.preCreateHook { err := h(obj) if err != nil { return err } } - return c.f.Create(ctx, obj, opts...) + + err := c.f.Create(ctx, obj, opts...) + if err != nil { + return err + } + + for _, h := range c.postCreateHook { + err := h(obj) + if err != nil { + return err + } + } + return nil } // See [sigs.k8s.io/controller-runtime/pkg/client.Writer.Delete] func (c *HookingClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - for _, h := range c.deleteHook { + for _, h := range c.preDeleteHook { err := h(obj) if err != nil { return err } } - return c.f.Delete(ctx, obj, opts...) + + err := c.f.Delete(ctx, obj, opts...) + if err != nil { + return err + } + + for _, h := range c.postDeleteHook { + err := h(obj) + if err != nil { + return err + } + } + return nil } // See [sigs.k8s.io/controller-runtime/pkg/client.Writer.DeleteAllOf] @@ -112,24 +187,48 @@ func (c *HookingClient) DeleteAllOf(ctx context.Context, obj client.Object, opts // See [sigs.k8s.io/controller-runtime/pkg/client.Writer.Update] func (c *HookingClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - for _, h := range c.updateHook { + for _, h := range c.preUpdateHook { err := h(obj) if err != nil { return err } } - return c.f.Update(ctx, obj, opts...) + + err := c.f.Update(ctx, obj, opts...) + if err != nil { + return err + } + + for _, h := range c.postUpdateHook { + err := h(obj) + if err != nil { + return err + } + } + return nil } // See [sigs.k8s.io/controller-runtime/pkg/client.Writer.Patch] func (c *HookingClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - for _, h := range c.patchHook { + for _, h := range c.prePatchHook { err := h(obj) if err != nil { return err } } - return c.f.Patch(ctx, obj, patch, opts...) + + err := c.f.Patch(ctx, obj, patch, opts...) + if err != nil { + return err + } + + for _, h := range c.postPatchHook { + err := h(obj) + if err != nil { + return err + } + } + return nil } // See [sigs.k8s.io/controller-runtime/pkg/client.Client.Scheme] diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/to/doc.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/to/doc.go new file mode 100644 index 000000000..faa98c9dc --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/to/doc.go @@ -0,0 +1,9 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright 2017 Microsoft Corporation. All rights reserved. +// Use of this source code is governed by an MIT +// license that can be found in the LICENSE file. + +// Package to contains various type-conversion helper functions. +package to diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/to/to.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/to/to.go new file mode 100644 index 000000000..e0e4817b9 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/to/to.go @@ -0,0 +1,21 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package to + +// Ptr returns a pointer to the provided value. +func Ptr[T any](v T) *T { + return &v +} + +// SliceOfPtrs returns a slice of *T from the specified values. +func SliceOfPtrs[T any](vv ...T) []*T { + slc := make([]*T, len(vv)) + for i := range vv { + slc[i] = Ptr(vv[i]) + } + return slc +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md index ddb24d810..7ea119ab3 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md @@ -1,10 +1,53 @@ # Release History +## 1.4.0 (2023-10-10) + +### Bugs Fixed +* `ManagedIdentityCredential` will now retry when IMDS responds 410 or 503 + +## 1.4.0-beta.5 (2023-09-12) + +### Features Added +* Service principal credentials can request CAE tokens + +### Breaking Changes +> These changes affect only code written against a beta version such as v1.4.0-beta.4 +* Whether `GetToken` requests a CAE token is now determined by `TokenRequestOptions.EnableCAE`. Azure + SDK clients which support CAE will set this option automatically. Credentials no longer request CAE + tokens by default or observe the environment variable "AZURE_IDENTITY_DISABLE_CP1". + +### Bugs Fixed +* Credential chains such as `DefaultAzureCredential` now try their next credential, if any, when + managed identity authentication fails in a Docker Desktop container + ([#21417](https://github.com/Azure/azure-sdk-for-go/issues/21417)) + +## 1.4.0-beta.4 (2023-08-16) + +### Other Changes +* Upgraded dependencies + ## 1.3.1 (2023-08-16) ### Other Changes * Upgraded dependencies +## 1.4.0-beta.3 (2023-08-08) + +### Bugs Fixed +* One invocation of `AzureCLICredential.GetToken()` and `OnBehalfOfCredential.GetToken()` + can no longer make two authentication attempts + +## 1.4.0-beta.2 (2023-07-14) + +### Other Changes +* `DefaultAzureCredentialOptions.TenantID` applies to workload identity authentication +* Upgraded dependencies + +## 1.4.0-beta.1 (2023-06-06) + +### Other Changes +* Re-enabled CAE support as in v1.3.0-beta.3 + ## 1.3.0 (2023-05-09) ### Breaking Changes diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md index 7b7515eba..fef099813 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md @@ -76,12 +76,14 @@ azlog.SetListener(func(event azlog.Event, s string) { azlog.SetEvents(azidentity.EventAuthentication) ``` + ## Troubleshoot DefaultAzureCredential authentication issues | Error |Description| Mitigation | |---|---|---| |"DefaultAzureCredential failed to acquire a token"|No credential in the `DefaultAzureCredential` chain provided a token|