Repository: Azure/ARO-RP
Pull request #2174 (2022-06-13 06:03:35)

Deregister cluster from hive
### Which issue this PR addresses:

https://msazure.visualstudio.com/AzureRedHatOpenShift/_workitems/edit/14319615/

Depends on https://github.com/Azure/ARO-RP/pull/2145  ! 

Adds the deregistration of a cluster from hive (deletion of the corresponding namespace) at cluster deletion time

### Test plan for issue:

Draft, unit tests to follow

### Is there any documentation that needs to be updated for this PR?

tbc


github-actions[bot]: Please rebase pull request.
m1kola: /azp run e2e

#2145 merged. Rebased on top of the master branch.
m1kola: /azp run ci
m1kola: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

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

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

</samp>

Patch:
diff --git a/.sha256sum b/.sha256sum
index d3c249a31..ada3d593a 100644
--- a/.sha256sum
+++ b/.sha256sum
@@ -1,3 +1,4 @@
 468fa0da0a50d50640ec57843ad288af343128b39f5bf23e76e4e336580883d4  swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2020-04-30/redhatopenshift.json
 c323c84befa5ea11da50a2407050abed6540ea01e796720bc2241604ce80567c  swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/preview/2021-09-01-preview/redhatopenshift.json
 e4e522e41855de71c0318db31cbd96c8713e7a74e7c81911bb494f0b194b3f43  swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-04-01/redhatopenshift.json
+895ec8758158ab788088bc92eb2a2c4aa7a448bc53192aea4cb38efa033fdd2d  swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/redhatopenshift.json
diff --git a/Makefile b/Makefile
index 26884be97..cd29e2836 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,7 @@ clean:
 	find -type d -name 'gomock_reflect_[0-9]*' -exec rm -rf {} \+ 2>/dev/null
 
 client: generate
-	hack/build-client.sh "${AUTOREST_IMAGE}" 2020-04-30 2021-09-01-preview 2022-04-01
+	hack/build-client.sh "${AUTOREST_IMAGE}" 2020-04-30 2021-09-01-preview 2022-04-01 2022-09-04
 
 # TODO: hard coding dev-config.yaml is clunky; it is also probably convenient to
 # override COMMIT.
diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go
index 0512f20c7..e557628f4 100644
--- a/cmd/aro/rp.go
+++ b/cmd/aro/rp.go
@@ -20,6 +20,7 @@ import (
 	_ "github.com/Azure/ARO-RP/pkg/api/v20200430"
 	_ "github.com/Azure/ARO-RP/pkg/api/v20210901preview"
 	_ "github.com/Azure/ARO-RP/pkg/api/v20220401"
+	_ "github.com/Azure/ARO-RP/pkg/api/v20220904"
 	"github.com/Azure/ARO-RP/pkg/backend"
 	"github.com/Azure/ARO-RP/pkg/database"
 	"github.com/Azure/ARO-RP/pkg/env"
diff --git a/pkg/api/v20220904/generate.go b/pkg/api/v20220904/generate.go
new file mode 100644
index 000000000..7f3265594
--- /dev/null
+++ b/pkg/api/v20220904/generate.go
@@ -0,0 +1,6 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+//go:generate go run ../../../hack/swagger github.com/Azure/ARO-RP/pkg/api/v20220904 ../../../swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04
diff --git a/pkg/api/v20220904/openshiftcluster.go b/pkg/api/v20220904/openshiftcluster.go
new file mode 100644
index 000000000..113eedb66
--- /dev/null
+++ b/pkg/api/v20220904/openshiftcluster.go
@@ -0,0 +1,248 @@
+package v20220904
+
+import "time"
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+// OpenShiftClusterList represents a list of OpenShift clusters.
+type OpenShiftClusterList struct {
+	// The list of OpenShift clusters.
+	OpenShiftClusters []*OpenShiftCluster `json:"value"`
+
+	// The link used to get the next page of operations.
+	NextLink string `json:"nextLink,omitempty"`
+}
+
+// OpenShiftCluster represents an Azure Red Hat OpenShift cluster.
+type OpenShiftCluster struct {
+	// The resource ID.
+	ID string `json:"id,omitempty" mutable:"case"`
+
+	// The resource name.
+	Name string `json:"name,omitempty" mutable:"case"`
+
+	// The resource type.
+	Type string `json:"type,omitempty" mutable:"case"`
+
+	// The resource location.
+	Location string `json:"location,omitempty"`
+
+	// SystemData - The system metadata relating to this resource
+	SystemData *SystemData `json:"systemData,omitempty"`
+
+	// The resource tags.
+	Tags Tags `json:"tags,omitempty" mutable:"true"`
+
+	// The cluster properties.
+	Properties OpenShiftClusterProperties `json:"properties,omitempty"`
+}
+
+// Tags represents an OpenShift cluster's tags.
+type Tags map[string]string
+
+// OpenShiftClusterProperties represents an OpenShift cluster's properties.
+type OpenShiftClusterProperties struct {
+	// The cluster provisioning state.
+	ProvisioningState ProvisioningState `json:"provisioningState,omitempty"`
+
+	// The cluster profile.
+	ClusterProfile ClusterProfile `json:"clusterProfile,omitempty"`
+
+	// The console profile.
+	ConsoleProfile ConsoleProfile `json:"consoleProfile,omitempty"`
+
+	// The cluster service principal profile.
+	ServicePrincipalProfile ServicePrincipalProfile `json:"servicePrincipalProfile,omitempty"`
+
+	// The cluster network profile.
+	NetworkProfile NetworkProfile `json:"networkProfile,omitempty"`
+
+	// The cluster master profile.
+	MasterProfile MasterProfile `json:"masterProfile,omitempty"`
+
+	// The cluster worker profiles.
+	WorkerProfiles []WorkerProfile `json:"workerProfiles,omitempty"`
+
+	// The cluster API server profile.
+	APIServerProfile APIServerProfile `json:"apiserverProfile,omitempty"`
+
+	// The cluster ingress profiles.
+	IngressProfiles []IngressProfile `json:"ingressProfiles,omitempty"`
+}
+
+// ProvisioningState represents a provisioning state.
+type ProvisioningState string
+
+// ProvisioningState constants.
+const (
+	ProvisioningStateCreating      ProvisioningState = "Creating"
+	ProvisioningStateUpdating      ProvisioningState = "Updating"
+	ProvisioningStateAdminUpdating ProvisioningState = "AdminUpdating"
+	ProvisioningStateDeleting      ProvisioningState = "Deleting"
+	ProvisioningStateSucceeded     ProvisioningState = "Succeeded"
+	ProvisioningStateFailed        ProvisioningState = "Failed"
+)
+
+// FipsValidatedModules determines if FIPS is used.
+type FipsValidatedModules string
+
+// FipsValidatedModules constants.
+const (
+	FipsValidatedModulesEnabled  FipsValidatedModules = "Enabled"
+	FipsValidatedModulesDisabled FipsValidatedModules = "Disabled"
+)
+
+// ClusterProfile represents a cluster profile.
+type ClusterProfile struct {
+	// The pull secret for the cluster.
+	PullSecret string `json:"pullSecret,omitempty"`
+
+	// The domain for the cluster.
+	Domain string `json:"domain,omitempty"`
+
+	// The version of the cluster.
+	Version string `json:"version,omitempty"`
+
+	// The ID of the cluster resource group.
+	ResourceGroupID string `json:"resourceGroupId,omitempty"`
+
+	// If FIPS validated crypto modules are used
+	FipsValidatedModules FipsValidatedModules `json:"fipsValidatedModules,omitempty"`
+}
+
+// ConsoleProfile represents a console profile.
+type ConsoleProfile struct {
+	// The URL to access the cluster console.
+	URL string `json:"url,omitempty"`
+}
+
+// ServicePrincipalProfile represents a service principal profile.
+type ServicePrincipalProfile struct {
+	// The client ID used for the cluster.
+	ClientID string `json:"clientId,omitempty" mutable:"true"`
+
+	// The client secret used for the cluster.
+	ClientSecret string `json:"clientSecret,omitempty" mutable:"true"`
+}
+
+// NetworkProfile represents a network profile.
+type NetworkProfile struct {
+	// The CIDR used for OpenShift/Kubernetes Pods.
+	PodCIDR string `json:"podCidr,omitempty"`
+
+	// The CIDR used for OpenShift/Kubernetes Services.
+	ServiceCIDR string `json:"serviceCidr,omitempty"`
+}
+
+// EncryptionAtHost represents encryption at host state
+type EncryptionAtHost string
+
+// EncryptionAtHost constants
+const (
+	EncryptionAtHostEnabled  EncryptionAtHost = "Enabled"
+	EncryptionAtHostDisabled EncryptionAtHost = "Disabled"
+)
+
+// MasterProfile represents a master profile.
+type MasterProfile struct {
+	// The size of the master VMs.
+	VMSize VMSize `json:"vmSize,omitempty"`
+
+	// The Azure resource ID of the master subnet.
+	SubnetID string `json:"subnetId,omitempty"`
+
+	// Whether master virtual machines are encrypted at host.
+	EncryptionAtHost EncryptionAtHost `json:"encryptionAtHost,omitempty"`
+
+	// The resource ID of an associated DiskEncryptionSet, if applicable.
+	DiskEncryptionSetID string `json:"diskEncryptionSetId,omitempty"`
+}
+
+// VM size availability varies by region.
+// If a node contains insufficient compute resources (memory, cpu, etc.), pods might fail to run correctly.
+// For more details on restricted VM sizes, see: https://docs.microsoft.com/en-us/azure/openshift/support-policies-v4#supported-virtual-machine-sizes
+type VMSize string
+
+// WorkerProfile represents a worker profile.
+type WorkerProfile struct {
+	// The worker profile name.
+	Name string `json:"name,omitempty"`
+
+	// The size of the worker VMs.
+	VMSize VMSize `json:"vmSize,omitempty"`
+
+	// The disk size of the worker VMs.
+	DiskSizeGB int `json:"diskSizeGB,omitempty"`
+
+	// The Azure resource ID of the worker subnet.
+	SubnetID string `json:"subnetId,omitempty"`
+
+	// The number of worker VMs.
+	Count int `json:"count,omitempty"`
+
+	// Whether master virtual machines are encrypted at host.
+	EncryptionAtHost EncryptionAtHost `json:"encryptionAtHost,omitempty"`
+
+	// The resource ID of an associated DiskEncryptionSet, if applicable.
+	DiskEncryptionSetID string `json:"diskEncryptionSetId,omitempty"`
+}
+
+// APIServerProfile represents an API server profile.
+type APIServerProfile struct {
+	// API server visibility.
+	Visibility Visibility `json:"visibility,omitempty"`
+
+	// The URL to access the cluster API server.
+	URL string `json:"url,omitempty"`
+
+	// The IP of the cluster API server.
+	IP string `json:"ip,omitempty"`
+}
+
+// Visibility represents visibility.
+type Visibility string
+
+// Visibility constants
+const (
+	VisibilityPublic  Visibility = "Public"
+	VisibilityPrivate Visibility = "Private"
+)
+
+// IngressProfile represents an ingress profile.
+type IngressProfile struct {
+	// The ingress profile name.
+	Name string `json:"name,omitempty"`
+
+	// Ingress visibility.
+	Visibility Visibility `json:"visibility,omitempty"`
+
+	// The IP of the ingress.
+	IP string `json:"ip,omitempty"`
+}
+
+// CreatedByType by defines user type, which executed the request
+type CreatedByType string
+
+const (
+	CreatedByTypeApplication     CreatedByType = "Application"
+	CreatedByTypeKey             CreatedByType = "Key"
+	CreatedByTypeManagedIdentity CreatedByType = "ManagedIdentity"
+	CreatedByTypeUser            CreatedByType = "User"
+)
+
+// SystemData metadata pertaining to creation and last modification of the resource.
+type SystemData struct {
+	// The identity that created the resource.
+	CreatedBy string `json:"createdBy,omitempty"`
+	// The type of identity that created the resource. Possible values include: 'User', 'Application', 'ManagedIdentity', 'Key'
+	CreatedByType CreatedByType `json:"createdByType,omitempty"`
+	// The timestamp of resource creation (UTC).
+	CreatedAt *time.Time `json:"createdAt,omitempty"`
+	// The identity that last modified the resource.
+	LastModifiedBy string `json:"lastModifiedBy,omitempty"`
+	// The type of identity that last modified the resource. Possible values include: 'User', 'Application', 'ManagedIdentity', 'Key'
+	LastModifiedByType CreatedByType `json:"lastModifiedByType,omitempty"`
+	// The type of identity that last modified the resource.
+	LastModifiedAt *time.Time `json:"lastModifiedAt,omitempty"`
+}
diff --git a/pkg/api/v20220904/openshiftcluster_convert.go b/pkg/api/v20220904/openshiftcluster_convert.go
new file mode 100644
index 000000000..6e7c7f105
--- /dev/null
+++ b/pkg/api/v20220904/openshiftcluster_convert.go
@@ -0,0 +1,183 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"github.com/Azure/ARO-RP/pkg/api"
+)
+
+type openShiftClusterConverter struct{}
+
+// 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 *openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interface{} {
+	out := &OpenShiftCluster{
+		ID:       oc.ID,
+		Name:     oc.Name,
+		Type:     oc.Type,
+		Location: oc.Location,
+		Properties: OpenShiftClusterProperties{
+			ProvisioningState: ProvisioningState(oc.Properties.ProvisioningState),
+			ClusterProfile: ClusterProfile{
+				PullSecret:           string(oc.Properties.ClusterProfile.PullSecret),
+				Domain:               oc.Properties.ClusterProfile.Domain,
+				Version:              oc.Properties.ClusterProfile.Version,
+				ResourceGroupID:      oc.Properties.ClusterProfile.ResourceGroupID,
+				FipsValidatedModules: FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules),
+			},
+			ConsoleProfile: ConsoleProfile{
+				URL: oc.Properties.ConsoleProfile.URL,
+			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientID:     oc.Properties.ServicePrincipalProfile.ClientID,
+				ClientSecret: string(oc.Properties.ServicePrincipalProfile.ClientSecret),
+			},
+			NetworkProfile: NetworkProfile{
+				PodCIDR:     oc.Properties.NetworkProfile.PodCIDR,
+				ServiceCIDR: oc.Properties.NetworkProfile.ServiceCIDR,
+			},
+			MasterProfile: MasterProfile{
+				VMSize:              VMSize(oc.Properties.MasterProfile.VMSize),
+				SubnetID:            oc.Properties.MasterProfile.SubnetID,
+				EncryptionAtHost:    EncryptionAtHost(oc.Properties.MasterProfile.EncryptionAtHost),
+				DiskEncryptionSetID: oc.Properties.MasterProfile.DiskEncryptionSetID,
+			},
+			APIServerProfile: APIServerProfile{
+				Visibility: Visibility(oc.Properties.APIServerProfile.Visibility),
+				URL:        oc.Properties.APIServerProfile.URL,
+				IP:         oc.Properties.APIServerProfile.IP,
+			},
+		},
+	}
+
+	if oc.Properties.WorkerProfiles != nil {
+		out.Properties.WorkerProfiles = make([]WorkerProfile, 0, len(oc.Properties.WorkerProfiles))
+		for _, p := range oc.Properties.WorkerProfiles {
+			out.Properties.WorkerProfiles = append(out.Properties.WorkerProfiles, WorkerProfile{
+				Name:                p.Name,
+				VMSize:              VMSize(p.VMSize),
+				DiskSizeGB:          p.DiskSizeGB,
+				SubnetID:            p.SubnetID,
+				Count:               p.Count,
+				EncryptionAtHost:    EncryptionAtHost(p.EncryptionAtHost),
+				DiskEncryptionSetID: p.DiskEncryptionSetID,
+			})
+		}
+	}
+
+	if oc.Properties.IngressProfiles != nil {
+		out.Properties.IngressProfiles = make([]IngressProfile, 0, len(oc.Properties.IngressProfiles))
+		for _, p := range oc.Properties.IngressProfiles {
+			out.Properties.IngressProfiles = append(out.Properties.IngressProfiles, IngressProfile{
+				Name:       p.Name,
+				Visibility: Visibility(p.Visibility),
+				IP:         p.IP,
+			})
+		}
+	}
+
+	if oc.Tags != nil {
+		out.Tags = make(map[string]string, len(oc.Tags))
+		for k, v := range oc.Tags {
+			out.Tags[k] = v
+		}
+	}
+
+	out.SystemData = &SystemData{
+		CreatedBy:          oc.SystemData.CreatedBy,
+		CreatedAt:          oc.SystemData.CreatedAt,
+		CreatedByType:      CreatedByType(oc.SystemData.CreatedByType),
+		LastModifiedBy:     oc.SystemData.LastModifiedBy,
+		LastModifiedAt:     oc.SystemData.LastModifiedAt,
+		LastModifiedByType: CreatedByType(oc.SystemData.LastModifiedByType),
+	}
+
+	return out
+}
+
+// ToExternalList returns a slice of external representations of the internal
+// objects
+func (c *openShiftClusterConverter) ToExternalList(ocs []*api.OpenShiftCluster, nextLink string) interface{} {
+	l := &OpenShiftClusterList{
+		OpenShiftClusters: make([]*OpenShiftCluster, 0, len(ocs)),
+		NextLink:          nextLink,
+	}
+
+	for _, oc := range ocs {
+		l.OpenShiftClusters = append(l.OpenShiftClusters, c.ToExternal(oc).(*OpenShiftCluster))
+	}
+
+	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 *openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShiftCluster) {
+	oc := _oc.(*OpenShiftCluster)
+
+	out.ID = oc.ID
+	out.Name = oc.Name
+	out.Type = oc.Type
+	out.Location = oc.Location
+	out.Tags = nil
+	if oc.Tags != nil {
+		out.Tags = make(map[string]string, len(oc.Tags))
+		for k, v := range oc.Tags {
+			out.Tags[k] = v
+		}
+	}
+	out.Properties.ProvisioningState = api.ProvisioningState(oc.Properties.ProvisioningState)
+	out.Properties.ClusterProfile.PullSecret = api.SecureString(oc.Properties.ClusterProfile.PullSecret)
+	out.Properties.ClusterProfile.Domain = oc.Properties.ClusterProfile.Domain
+	out.Properties.ClusterProfile.Version = oc.Properties.ClusterProfile.Version
+	out.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID
+	out.Properties.ConsoleProfile.URL = oc.Properties.ConsoleProfile.URL
+	out.Properties.ClusterProfile.FipsValidatedModules = api.FipsValidatedModules(oc.Properties.ClusterProfile.FipsValidatedModules)
+	out.Properties.ServicePrincipalProfile.ClientID = oc.Properties.ServicePrincipalProfile.ClientID
+	out.Properties.ServicePrincipalProfile.ClientSecret = api.SecureString(oc.Properties.ServicePrincipalProfile.ClientSecret)
+	out.Properties.NetworkProfile.PodCIDR = oc.Properties.NetworkProfile.PodCIDR
+	out.Properties.NetworkProfile.ServiceCIDR = oc.Properties.NetworkProfile.ServiceCIDR
+	out.Properties.MasterProfile.VMSize = api.VMSize(oc.Properties.MasterProfile.VMSize)
+	out.Properties.MasterProfile.SubnetID = oc.Properties.MasterProfile.SubnetID
+	out.Properties.MasterProfile.EncryptionAtHost = api.EncryptionAtHost(oc.Properties.MasterProfile.EncryptionAtHost)
+	out.Properties.MasterProfile.DiskEncryptionSetID = oc.Properties.MasterProfile.DiskEncryptionSetID
+	out.Properties.WorkerProfiles = nil
+	if oc.Properties.WorkerProfiles != nil {
+		out.Properties.WorkerProfiles = make([]api.WorkerProfile, len(oc.Properties.WorkerProfiles))
+		for i := range oc.Properties.WorkerProfiles {
+			out.Properties.WorkerProfiles[i].Name = oc.Properties.WorkerProfiles[i].Name
+			out.Properties.WorkerProfiles[i].VMSize = api.VMSize(oc.Properties.WorkerProfiles[i].VMSize)
+			out.Properties.WorkerProfiles[i].DiskSizeGB = oc.Properties.WorkerProfiles[i].DiskSizeGB
+			out.Properties.WorkerProfiles[i].SubnetID = oc.Properties.WorkerProfiles[i].SubnetID
+			out.Properties.WorkerProfiles[i].Count = oc.Properties.WorkerProfiles[i].Count
+			out.Properties.WorkerProfiles[i].EncryptionAtHost = api.EncryptionAtHost(oc.Properties.WorkerProfiles[i].EncryptionAtHost)
+			out.Properties.WorkerProfiles[i].DiskEncryptionSetID = oc.Properties.WorkerProfiles[i].DiskEncryptionSetID
+		}
+	}
+	out.Properties.APIServerProfile.Visibility = api.Visibility(oc.Properties.APIServerProfile.Visibility)
+	out.Properties.APIServerProfile.URL = oc.Properties.APIServerProfile.URL
+	out.Properties.APIServerProfile.IP = oc.Properties.APIServerProfile.IP
+	out.Properties.IngressProfiles = nil
+	if oc.Properties.IngressProfiles != nil {
+		out.Properties.IngressProfiles = make([]api.IngressProfile, len(oc.Properties.IngressProfiles))
+		for i := range oc.Properties.IngressProfiles {
+			out.Properties.IngressProfiles[i].Name = oc.Properties.IngressProfiles[i].Name
+			out.Properties.IngressProfiles[i].Visibility = api.Visibility(oc.Properties.IngressProfiles[i].Visibility)
+			out.Properties.IngressProfiles[i].IP = oc.Properties.IngressProfiles[i].IP
+		}
+	}
+
+	out.SystemData = api.SystemData{
+		CreatedBy:          oc.SystemData.CreatedBy,
+		CreatedAt:          oc.SystemData.CreatedAt,
+		CreatedByType:      api.CreatedByType(oc.SystemData.CreatedByType),
+		LastModifiedBy:     oc.SystemData.LastModifiedBy,
+		LastModifiedAt:     oc.SystemData.LastModifiedAt,
+		LastModifiedByType: api.CreatedByType(oc.SystemData.CreatedByType),
+	}
+}
diff --git a/pkg/api/v20220904/openshiftcluster_example.go b/pkg/api/v20220904/openshiftcluster_example.go
new file mode 100644
index 000000000..d239d90bc
--- /dev/null
+++ b/pkg/api/v20220904/openshiftcluster_example.go
@@ -0,0 +1,63 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"github.com/Azure/ARO-RP/pkg/api"
+)
+
+func exampleOpenShiftCluster() *OpenShiftCluster {
+	doc := api.ExampleOpenShiftClusterDocument()
+	return (&openShiftClusterConverter{}).ToExternal(doc.OpenShiftCluster).(*OpenShiftCluster)
+}
+
+// ExampleOpenShiftClusterPatchParameter returns an example OpenShiftCluster
+// object that an end-user might send to create a cluster in a PATCH request
+func ExampleOpenShiftClusterPatchParameter() interface{} {
+	oc := ExampleOpenShiftClusterPutParameter().(*OpenShiftCluster)
+	oc.Location = ""
+	oc.SystemData = nil
+
+	return oc
+}
+
+// ExampleOpenShiftClusterPutParameter returns an example OpenShiftCluster
+// object that an end-user might send to create a cluster in a PUT request
+func ExampleOpenShiftClusterPutParameter() interface{} {
+	oc := exampleOpenShiftCluster()
+	oc.ID = ""
+	oc.Name = ""
+	oc.Type = ""
+	oc.Properties.ProvisioningState = ""
+	oc.Properties.ClusterProfile.Version = ""
+	oc.Properties.ClusterProfile.FipsValidatedModules = FipsValidatedModules(FipsValidatedModulesEnabled)
+	oc.Properties.ConsoleProfile.URL = ""
+	oc.Properties.APIServerProfile.URL = ""
+	oc.Properties.APIServerProfile.IP = ""
+	oc.Properties.IngressProfiles[0].IP = ""
+	oc.Properties.MasterProfile.EncryptionAtHost = EncryptionAtHost(EncryptionAtHostEnabled)
+	oc.SystemData = nil
+
+	return oc
+}
+
+// ExampleOpenShiftClusterResponse returns an example OpenShiftCluster object
+// that the RP might return to an end-user
+func ExampleOpenShiftClusterResponse() interface{} {
+	oc := exampleOpenShiftCluster()
+	oc.Properties.ClusterProfile.PullSecret = ""
+	oc.Properties.ServicePrincipalProfile.ClientSecret = ""
+
+	return oc
+}
+
+// ExampleOpenShiftClusterListResponse returns an example OpenShiftClusterList
+// object that the RP might return to an end-user
+func ExampleOpenShiftClusterListResponse() interface{} {
+	return &OpenShiftClusterList{
+		OpenShiftClusters: []*OpenShiftCluster{
+			ExampleOpenShiftClusterResponse().(*OpenShiftCluster),
+		},
+	}
+}
diff --git a/pkg/api/v20220904/openshiftcluster_marshal.go b/pkg/api/v20220904/openshiftcluster_marshal.go
new file mode 100644
index 000000000..7e9ee2ff4
--- /dev/null
+++ b/pkg/api/v20220904/openshiftcluster_marshal.go
@@ -0,0 +1,20 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"encoding/json"
+)
+
+// UnmarshalJSON unmarshals tags.  We override this to ensure that PATCH
+// behaviour overwrites an existing tags map rather than endlessly adding to it
+func (t *Tags) UnmarshalJSON(b []byte) error {
+	var m map[string]string
+	err := json.Unmarshal(b, &m)
+	if err != nil {
+		return err
+	}
+	*t = m
+	return nil
+}
diff --git a/pkg/api/v20220904/openshiftclusteradminkubeconfig.go b/pkg/api/v20220904/openshiftclusteradminkubeconfig.go
new file mode 100644
index 000000000..e5f4e9b89
--- /dev/null
+++ b/pkg/api/v20220904/openshiftclusteradminkubeconfig.go
@@ -0,0 +1,10 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+// OpenShiftClusterAdminKubeconfig represents an OpenShift cluster's admin kubeconfig.
+type OpenShiftClusterAdminKubeconfig struct {
+	// The base64-encoded kubeconfig file.
+	Kubeconfig []byte `json:"kubeconfig,omitempty"`
+}
diff --git a/pkg/api/v20220904/openshiftclusteradminkubeconfig_convert.go b/pkg/api/v20220904/openshiftclusteradminkubeconfig_convert.go
new file mode 100644
index 000000000..92ebe0d2f
--- /dev/null
+++ b/pkg/api/v20220904/openshiftclusteradminkubeconfig_convert.go
@@ -0,0 +1,21 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"github.com/Azure/ARO-RP/pkg/api"
+)
+
+type openShiftClusterAdminKubeconfigConverter struct{}
+
+// openShiftClusterAdminKubeconfigConverter 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 (*openShiftClusterAdminKubeconfigConverter) ToExternal(oc *api.OpenShiftCluster) interface{} {
+	return &OpenShiftClusterAdminKubeconfig{
+		Kubeconfig: oc.Properties.UserAdminKubeconfig,
+	}
+}
diff --git a/pkg/api/v20220904/openshiftclusteradminkubeconfig_example.go b/pkg/api/v20220904/openshiftclusteradminkubeconfig_example.go
new file mode 100644
index 000000000..4f518128f
--- /dev/null
+++ b/pkg/api/v20220904/openshiftclusteradminkubeconfig_example.go
@@ -0,0 +1,12 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+// ExampleOpenShiftClusterAdminKubeconfigResponse returns an example
+// OpenShiftClusterAdminKubeconfig object that the RP might return to an end-user
+func ExampleOpenShiftClusterAdminKubeconfigResponse() interface{} {
+	return &OpenShiftClusterAdminKubeconfig{
+		Kubeconfig: []byte("{}"),
+	}
+}
diff --git a/pkg/api/v20220904/openshiftclustercredentials.go b/pkg/api/v20220904/openshiftclustercredentials.go
new file mode 100644
index 000000000..35d2f8964
--- /dev/null
+++ b/pkg/api/v20220904/openshiftclustercredentials.go
@@ -0,0 +1,13 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+// OpenShiftClusterCredentials represents an OpenShift cluster's credentials.
+type OpenShiftClusterCredentials struct {
+	// The username for the kubeadmin user.
+	KubeadminUsername string `json:"kubeadminUsername,omitempty"`
+
+	// The password for the kubeadmin user.
+	KubeadminPassword string `json:"kubeadminPassword,omitempty"`
+}
diff --git a/pkg/api/v20220904/openshiftclustercredentials_convert.go b/pkg/api/v20220904/openshiftclustercredentials_convert.go
new file mode 100644
index 000000000..477cd1f74
--- /dev/null
+++ b/pkg/api/v20220904/openshiftclustercredentials_convert.go
@@ -0,0 +1,24 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"github.com/Azure/ARO-RP/pkg/api"
+)
+
+type openShiftClusterCredentialsConverter struct{}
+
+// OpenShiftClusterCredentialsToExternal 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 (*openShiftClusterCredentialsConverter) ToExternal(oc *api.OpenShiftCluster) interface{} {
+	out := &OpenShiftClusterCredentials{
+		KubeadminUsername: "kubeadmin",
+		KubeadminPassword: string(oc.Properties.KubeadminPassword),
+	}
+
+	return out
+}
diff --git a/pkg/api/v20220904/openshiftclustercredentials_example.go b/pkg/api/v20220904/openshiftclustercredentials_example.go
new file mode 100644
index 000000000..4a9a75ac6
--- /dev/null
+++ b/pkg/api/v20220904/openshiftclustercredentials_example.go
@@ -0,0 +1,13 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+// ExampleOpenShiftClusterCredentialsResponse returns an example
+// OpenShiftClusterCredentials object that the RP might return to an end-user
+func ExampleOpenShiftClusterCredentialsResponse() interface{} {
+	return &OpenShiftClusterCredentials{
+		KubeadminUsername: "kubeadmin",
+		KubeadminPassword: "password",
+	}
+}
diff --git a/pkg/api/v20220904/register.go b/pkg/api/v20220904/register.go
new file mode 100644
index 000000000..289fc5f28
--- /dev/null
+++ b/pkg/api/v20220904/register.go
@@ -0,0 +1,38 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"github.com/Azure/ARO-RP/pkg/api"
+)
+
+// APIVersion contains a version string as it will be used by clients
+const APIVersion = "2022-09-04"
+
+const (
+	resourceProviderNamespace = "Microsoft.RedHatOpenShift"
+	resourceType              = "openShiftClusters"
+)
+
+func init() {
+	api.APIs[APIVersion] = &api.Version{
+		OpenShiftClusterConverter: func() api.OpenShiftClusterConverter {
+			return &openShiftClusterConverter{}
+		},
+		OpenShiftClusterStaticValidator: func(location, domain string, requireD2sV3Workers bool, resourceID string) api.OpenShiftClusterStaticValidator {
+			return &openShiftClusterStaticValidator{
+				location:            location,
+				domain:              domain,
+				requireD2sV3Workers: requireD2sV3Workers,
+				resourceID:          resourceID,
+			}
+		},
+		OpenShiftClusterCredentialsConverter: func() api.OpenShiftClusterCredentialsConverter {
+			return &openShiftClusterCredentialsConverter{}
+		},
+		OpenShiftClusterAdminKubeconfigConverter: func() api.OpenShiftClusterAdminKubeconfigConverter {
+			return &openShiftClusterAdminKubeconfigConverter{}
+		},
+	}
+}
diff --git a/pkg/cluster/delete.go b/pkg/cluster/delete.go
index 8c58c0a51..c1d14e7e9 100644
--- a/pkg/cluster/delete.go
+++ b/pkg/cluster/delete.go
@@ -465,5 +465,12 @@ func (m *manager) Delete(ctx context.Context) error {
 		}
 	}
 
+	// Don't fail the deletion because of hive
+	// This should change when/if we start using hive for cluster deletion
+	err = m.deregisterClusterFromHive(ctx)
+	if err != nil {
+		m.log.Info(err)
+	}
+
 	return m.billing.Delete(ctx, m.doc)
 }
diff --git a/pkg/cluster/deregisterFromHive.go b/pkg/cluster/deregisterFromHive.go
new file mode 100644
index 000000000..530b989e3
--- /dev/null
+++ b/pkg/cluster/deregisterFromHive.go
@@ -0,0 +1,23 @@
+package cluster
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"errors"
+)
+
+func (m *manager) deregisterClusterFromHive(ctx context.Context) error {
+	if m.hiveClusterManager == nil {
+		return errors.New("no hive cluster manager, skipping")
+	}
+	namespace := m.doc.OpenShiftCluster.Properties.HiveProfile.Namespace
+
+	if namespace == "" {
+		m.log.Info("no hive namespace name in cluster document, skipping hive deregistration")
+		return nil
+	}
+
+	return m.hiveClusterManager.Delete(ctx, namespace)
+}
diff --git a/pkg/hive/manager.go b/pkg/hive/manager.go
index a14325550..c70dc6c50 100644
--- a/pkg/hive/manager.go
+++ b/pkg/hive/manager.go
@@ -25,6 +25,7 @@ type ClusterManager interface {
 	// Something similar to this: https://github.com/Azure/ARO-RP/pull/2145#discussion_r897915283
 	// TODO: Replace Register with CreateOrUpdate and remove the comment above
 	Register(ctx context.Context, workloadCluster *WorkloadCluster) (*hivev1.ClusterDeployment, error)
+	Delete(ctx context.Context, namespace string) error
 	IsConnected(ctx context.Context, namespace string) (bool, string, error)
 }
 
@@ -96,6 +97,11 @@ func (hr *clusterManager) Register(ctx context.Context, workloadCluster *Workloa
 	return hr.hiveClientset.HiveV1().ClusterDeployments(namespace).Create(ctx, cds, metav1.CreateOptions{})
 }
 
+func (hr *clusterManager) Delete(ctx context.Context, namespace string) error {
+	// Just deleting the namespace for now
+	return hr.kubernetescli.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{})
+}
+
 func (hr *clusterManager) IsConnected(ctx context.Context, namespace string) (bool, string, error) {
 	cd, err := hr.hiveClientset.HiveV1().ClusterDeployments(namespace).Get(ctx, clusterDeploymentName, metav1.GetOptions{})
 	if err != nil {
diff --git a/pkg/swagger/generator.go b/pkg/swagger/generator.go
index bd7b9f3f4..d1034fb9a 100644
--- a/pkg/swagger/generator.go
+++ b/pkg/swagger/generator.go
@@ -10,11 +10,13 @@ import (
 	v20200430 "github.com/Azure/ARO-RP/pkg/api/v20200430"
 	"github.com/Azure/ARO-RP/pkg/api/v20210901preview"
 	v20220401 "github.com/Azure/ARO-RP/pkg/api/v20220401"
+	v20220904 "github.com/Azure/ARO-RP/pkg/api/v20220904"
 )
 
 const apiv20200430Path = "github.com/Azure/ARO-RP/pkg/api/v20200430"
 const apiv20210901previewPath = "github.com/Azure/ARO-RP/pkg/api/v20210901preview"
 const apiv20220401Path = "github.com/Azure/ARO-RP/pkg/api/v20220401"
+const apiv20220904Path = "github.com/Azure/ARO-RP/pkg/api/v20220904"
 
 type generator struct {
 	exampleOpenShiftClusterPutParameter            func() interface{}
@@ -69,6 +71,22 @@ var apis = map[string]*generator{
 		exampleOpenShiftClusterAdminKubeconfigResponse: v20220401.ExampleOpenShiftClusterAdminKubeconfigResponse,
 		exampleOperationListResponse:                   api.ExampleOperationListResponse,
 
+		xmsEnum:            []string{"EncryptionAtHost", "FipsValidatedModules", "SoftwareDefinedNetwork", "Visibility"},
+		xmsSecretList:      []string{"kubeconfig", "kubeadminPassword"},
+		xmsIdentifiers:     []string{},
+		commonTypesVersion: "v2",
+		systemData:         true,
+		kubeConfig:         true,
+	},
+	apiv20220904Path: {
+		exampleOpenShiftClusterPutParameter:            v20220904.ExampleOpenShiftClusterPutParameter,
+		exampleOpenShiftClusterPatchParameter:          v20220904.ExampleOpenShiftClusterPatchParameter,
+		exampleOpenShiftClusterResponse:                v20220904.ExampleOpenShiftClusterResponse,
+		exampleOpenShiftClusterCredentialsResponse:     v20220904.ExampleOpenShiftClusterCredentialsResponse,
+		exampleOpenShiftClusterListResponse:            v20220904.ExampleOpenShiftClusterListResponse,
+		exampleOpenShiftClusterAdminKubeconfigResponse: v20220904.ExampleOpenShiftClusterAdminKubeconfigResponse,
+		exampleOperationListResponse:                   api.ExampleOperationListResponse,
+
 		xmsEnum:            []string{"EncryptionAtHost", "FipsValidatedModules", "SoftwareDefinedNetwork", "Visibility"},
 		xmsSecretList:      []string{"kubeconfig", "kubeadminPassword"},
 		xmsIdentifiers:     []string{},
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_CreateOrUpdate.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_CreateOrUpdate.json
new file mode 100644
index 000000000..b930710d3
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_CreateOrUpdate.json
@@ -0,0 +1,180 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup",
+    "resourceName": "resourceName",
+    "parameters": {
+      "location": "location",
+      "tags": {
+        "key": "value"
+      },
+      "properties": {
+        "clusterProfile": {
+          "pullSecret": "{\"auths\":{\"registry.connect.redhat.com\":{\"auth\":\"\"},\"registry.redhat.io\":{\"auth\":\"\"}}}",
+          "domain": "cluster.location.aroapp.io",
+          "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup",
+          "fipsValidatedModules": "Enabled"
+        },
+        "consoleProfile": {},
+        "servicePrincipalProfile": {
+          "clientId": "clientId",
+          "clientSecret": "clientSecret"
+        },
+        "networkProfile": {
+          "podCidr": "10.128.0.0/14",
+          "serviceCidr": "172.30.0.0/16"
+        },
+        "masterProfile": {
+          "vmSize": "Standard_D8s_v3",
+          "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master",
+          "encryptionAtHost": "Enabled"
+        },
+        "workerProfiles": [
+          {
+            "name": "worker",
+            "vmSize": "Standard_D2s_v3",
+            "diskSizeGB": 128,
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+            "count": 3
+          }
+        ],
+        "apiserverProfile": {
+          "visibility": "Public"
+        },
+        "ingressProfiles": [
+          {
+            "name": "default",
+            "visibility": "Public"
+          }
+        ]
+      }
+    }
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+        "name": "resourceName",
+        "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+        "location": "location",
+        "systemData": {
+          "createdBy": "string",
+          "createdByType": "Application",
+          "createdAt": "2020-02-03T01:01:01.1075056Z",
+          "lastModifiedBy": "string",
+          "lastModifiedByType": "Application",
+          "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+        },
+        "tags": {
+          "key": "value"
+        },
+        "properties": {
+          "provisioningState": "Succeeded",
+          "clusterProfile": {
+            "domain": "cluster.location.aroapp.io",
+            "version": "4.3.0",
+            "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+          },
+          "consoleProfile": {
+            "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+          },
+          "servicePrincipalProfile": {
+            "clientId": "clientId"
+          },
+          "networkProfile": {
+            "podCidr": "10.128.0.0/14",
+            "serviceCidr": "172.30.0.0/16"
+          },
+          "masterProfile": {
+            "vmSize": "Standard_D8s_v3",
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+          },
+          "workerProfiles": [
+            {
+              "name": "worker",
+              "vmSize": "Standard_D2s_v3",
+              "diskSizeGB": 128,
+              "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+              "count": 3
+            }
+          ],
+          "apiserverProfile": {
+            "visibility": "Public",
+            "url": "https://api.cluster.location.aroapp.io:6443/",
+            "ip": "1.2.3.4"
+          },
+          "ingressProfiles": [
+            {
+              "name": "default",
+              "visibility": "Public",
+              "ip": "1.2.3.4"
+            }
+          ]
+        }
+      }
+    },
+    "201": {
+      "body": {
+        "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+        "name": "resourceName",
+        "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+        "location": "location",
+        "systemData": {
+          "createdBy": "string",
+          "createdByType": "Application",
+          "createdAt": "2020-02-03T01:01:01.1075056Z",
+          "lastModifiedBy": "string",
+          "lastModifiedByType": "Application",
+          "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+        },
+        "tags": {
+          "key": "value"
+        },
+        "properties": {
+          "provisioningState": "Succeeded",
+          "clusterProfile": {
+            "domain": "cluster.location.aroapp.io",
+            "version": "4.3.0",
+            "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+          },
+          "consoleProfile": {
+            "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+          },
+          "servicePrincipalProfile": {
+            "clientId": "clientId"
+          },
+          "networkProfile": {
+            "podCidr": "10.128.0.0/14",
+            "serviceCidr": "172.30.0.0/16"
+          },
+          "masterProfile": {
+            "vmSize": "Standard_D8s_v3",
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+          },
+          "workerProfiles": [
+            {
+              "name": "worker",
+              "vmSize": "Standard_D2s_v3",
+              "diskSizeGB": 128,
+              "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+              "count": 3
+            }
+          ],
+          "apiserverProfile": {
+            "visibility": "Public",
+            "url": "https://api.cluster.location.aroapp.io:6443/",
+            "ip": "1.2.3.4"
+          },
+          "ingressProfiles": [
+            {
+              "name": "default",
+              "visibility": "Public",
+              "ip": "1.2.3.4"
+            }
+          ]
+        }
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Delete.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Delete.json
new file mode 100644
index 000000000..f0a504d0c
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Delete.json
@@ -0,0 +1,12 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup",
+    "resourceName": "resourceName"
+  },
+  "responses": {
+    "202": {},
+    "204": {}
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Get.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Get.json
new file mode 100644
index 000000000..e6a384ed6
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Get.json
@@ -0,0 +1,72 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup",
+    "resourceName": "resourceName"
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+        "name": "resourceName",
+        "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+        "location": "location",
+        "systemData": {
+          "createdBy": "string",
+          "createdByType": "Application",
+          "createdAt": "2020-02-03T01:01:01.1075056Z",
+          "lastModifiedBy": "string",
+          "lastModifiedByType": "Application",
+          "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+        },
+        "tags": {
+          "key": "value"
+        },
+        "properties": {
+          "provisioningState": "Succeeded",
+          "clusterProfile": {
+            "domain": "cluster.location.aroapp.io",
+            "version": "4.3.0",
+            "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+          },
+          "consoleProfile": {
+            "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+          },
+          "servicePrincipalProfile": {
+            "clientId": "clientId"
+          },
+          "networkProfile": {
+            "podCidr": "10.128.0.0/14",
+            "serviceCidr": "172.30.0.0/16"
+          },
+          "masterProfile": {
+            "vmSize": "Standard_D8s_v3",
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+          },
+          "workerProfiles": [
+            {
+              "name": "worker",
+              "vmSize": "Standard_D2s_v3",
+              "diskSizeGB": 128,
+              "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+              "count": 3
+            }
+          ],
+          "apiserverProfile": {
+            "visibility": "Public",
+            "url": "https://api.cluster.location.aroapp.io:6443/",
+            "ip": "1.2.3.4"
+          },
+          "ingressProfiles": [
+            {
+              "name": "default",
+              "visibility": "Public",
+              "ip": "1.2.3.4"
+            }
+          ]
+        }
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_List.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_List.json
new file mode 100644
index 000000000..604366426
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_List.json
@@ -0,0 +1,74 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId"
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "value": [
+          {
+            "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+            "name": "resourceName",
+            "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+            "location": "location",
+            "systemData": {
+              "createdBy": "string",
+              "createdByType": "Application",
+              "createdAt": "2020-02-03T01:01:01.1075056Z",
+              "lastModifiedBy": "string",
+              "lastModifiedByType": "Application",
+              "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+            },
+            "tags": {
+              "key": "value"
+            },
+            "properties": {
+              "provisioningState": "Succeeded",
+              "clusterProfile": {
+                "domain": "cluster.location.aroapp.io",
+                "version": "4.3.0",
+                "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+              },
+              "consoleProfile": {
+                "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+              },
+              "servicePrincipalProfile": {
+                "clientId": "clientId"
+              },
+              "networkProfile": {
+                "podCidr": "10.128.0.0/14",
+                "serviceCidr": "172.30.0.0/16"
+              },
+              "masterProfile": {
+                "vmSize": "Standard_D8s_v3",
+                "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+              },
+              "workerProfiles": [
+                {
+                  "name": "worker",
+                  "vmSize": "Standard_D2s_v3",
+                  "diskSizeGB": 128,
+                  "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+                  "count": 3
+                }
+              ],
+              "apiserverProfile": {
+                "visibility": "Public",
+                "url": "https://api.cluster.location.aroapp.io:6443/",
+                "ip": "1.2.3.4"
+              },
+              "ingressProfiles": [
+                {
+                  "name": "default",
+                  "visibility": "Public",
+                  "ip": "1.2.3.4"
+                }
+              ]
+            }
+          }
+        ]
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListAdminCredentials.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListAdminCredentials.json
new file mode 100644
index 000000000..6d66ba584
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListAdminCredentials.json
@@ -0,0 +1,15 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup",
+    "resourceName": "resourceName"
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "kubeconfig": "e30="
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListByResourceGroup.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListByResourceGroup.json
new file mode 100644
index 000000000..731b6a42c
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListByResourceGroup.json
@@ -0,0 +1,75 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup"
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "value": [
+          {
+            "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+            "name": "resourceName",
+            "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+            "location": "location",
+            "systemData": {
+              "createdBy": "string",
+              "createdByType": "Application",
+              "createdAt": "2020-02-03T01:01:01.1075056Z",
+              "lastModifiedBy": "string",
+              "lastModifiedByType": "Application",
+              "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+            },
+            "tags": {
+              "key": "value"
+            },
+            "properties": {
+              "provisioningState": "Succeeded",
+              "clusterProfile": {
+                "domain": "cluster.location.aroapp.io",
+                "version": "4.3.0",
+                "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+              },
+              "consoleProfile": {
+                "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+              },
+              "servicePrincipalProfile": {
+                "clientId": "clientId"
+              },
+              "networkProfile": {
+                "podCidr": "10.128.0.0/14",
+                "serviceCidr": "172.30.0.0/16"
+              },
+              "masterProfile": {
+                "vmSize": "Standard_D8s_v3",
+                "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+              },
+              "workerProfiles": [
+                {
+                  "name": "worker",
+                  "vmSize": "Standard_D2s_v3",
+                  "diskSizeGB": 128,
+                  "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+                  "count": 3
+                }
+              ],
+              "apiserverProfile": {
+                "visibility": "Public",
+                "url": "https://api.cluster.location.aroapp.io:6443/",
+                "ip": "1.2.3.4"
+              },
+              "ingressProfiles": [
+                {
+                  "name": "default",
+                  "visibility": "Public",
+                  "ip": "1.2.3.4"
+                }
+              ]
+            }
+          }
+        ]
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListCredentials.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListCredentials.json
new file mode 100644
index 000000000..b14349983
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_ListCredentials.json
@@ -0,0 +1,16 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup",
+    "resourceName": "resourceName"
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "kubeadminUsername": "kubeadmin",
+        "kubeadminPassword": "password"
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Update.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Update.json
new file mode 100644
index 000000000..05b026dbc
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/OpenShiftClusters_Update.json
@@ -0,0 +1,179 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04",
+    "subscriptionId": "subscriptionId",
+    "resourceGroupName": "resourceGroup",
+    "resourceName": "resourceName",
+    "parameters": {
+      "tags": {
+        "key": "value"
+      },
+      "properties": {
+        "clusterProfile": {
+          "pullSecret": "{\"auths\":{\"registry.connect.redhat.com\":{\"auth\":\"\"},\"registry.redhat.io\":{\"auth\":\"\"}}}",
+          "domain": "cluster.location.aroapp.io",
+          "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup",
+          "fipsValidatedModules": "Enabled"
+        },
+        "consoleProfile": {},
+        "servicePrincipalProfile": {
+          "clientId": "clientId",
+          "clientSecret": "clientSecret"
+        },
+        "networkProfile": {
+          "podCidr": "10.128.0.0/14",
+          "serviceCidr": "172.30.0.0/16"
+        },
+        "masterProfile": {
+          "vmSize": "Standard_D8s_v3",
+          "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master",
+          "encryptionAtHost": "Enabled"
+        },
+        "workerProfiles": [
+          {
+            "name": "worker",
+            "vmSize": "Standard_D2s_v3",
+            "diskSizeGB": 128,
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+            "count": 3
+          }
+        ],
+        "apiserverProfile": {
+          "visibility": "Public"
+        },
+        "ingressProfiles": [
+          {
+            "name": "default",
+            "visibility": "Public"
+          }
+        ]
+      }
+    }
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+        "name": "resourceName",
+        "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+        "location": "location",
+        "systemData": {
+          "createdBy": "string",
+          "createdByType": "Application",
+          "createdAt": "2020-02-03T01:01:01.1075056Z",
+          "lastModifiedBy": "string",
+          "lastModifiedByType": "Application",
+          "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+        },
+        "tags": {
+          "key": "value"
+        },
+        "properties": {
+          "provisioningState": "Succeeded",
+          "clusterProfile": {
+            "domain": "cluster.location.aroapp.io",
+            "version": "4.3.0",
+            "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+          },
+          "consoleProfile": {
+            "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+          },
+          "servicePrincipalProfile": {
+            "clientId": "clientId"
+          },
+          "networkProfile": {
+            "podCidr": "10.128.0.0/14",
+            "serviceCidr": "172.30.0.0/16"
+          },
+          "masterProfile": {
+            "vmSize": "Standard_D8s_v3",
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+          },
+          "workerProfiles": [
+            {
+              "name": "worker",
+              "vmSize": "Standard_D2s_v3",
+              "diskSizeGB": 128,
+              "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+              "count": 3
+            }
+          ],
+          "apiserverProfile": {
+            "visibility": "Public",
+            "url": "https://api.cluster.location.aroapp.io:6443/",
+            "ip": "1.2.3.4"
+          },
+          "ingressProfiles": [
+            {
+              "name": "default",
+              "visibility": "Public",
+              "ip": "1.2.3.4"
+            }
+          ]
+        }
+      }
+    },
+    "201": {
+      "body": {
+        "id": "/subscriptions/subscriptionId/resourceGroups/resourceGroup/providers/Microsoft.RedHatOpenShift/OpenShiftClusters/resourceName",
+        "name": "resourceName",
+        "type": "Microsoft.RedHatOpenShift/OpenShiftClusters",
+        "location": "location",
+        "systemData": {
+          "createdBy": "string",
+          "createdByType": "Application",
+          "createdAt": "2020-02-03T01:01:01.1075056Z",
+          "lastModifiedBy": "string",
+          "lastModifiedByType": "Application",
+          "lastModifiedAt": "2020-02-03T01:01:01.1075056Z"
+        },
+        "tags": {
+          "key": "value"
+        },
+        "properties": {
+          "provisioningState": "Succeeded",
+          "clusterProfile": {
+            "domain": "cluster.location.aroapp.io",
+            "version": "4.3.0",
+            "resourceGroupId": "/subscriptions/subscriptionId/resourceGroups/clusterResourceGroup"
+          },
+          "consoleProfile": {
+            "url": "https://console-openshift-console.apps.cluster.location.aroapp.io/"
+          },
+          "servicePrincipalProfile": {
+            "clientId": "clientId"
+          },
+          "networkProfile": {
+            "podCidr": "10.128.0.0/14",
+            "serviceCidr": "172.30.0.0/16"
+          },
+          "masterProfile": {
+            "vmSize": "Standard_D8s_v3",
+            "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/master"
+          },
+          "workerProfiles": [
+            {
+              "name": "worker",
+              "vmSize": "Standard_D2s_v3",
+              "diskSizeGB": 128,
+              "subnetId": "/subscriptions/subscriptionId/resourceGroups/vnetResourceGroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/worker",
+              "count": 3
+            }
+          ],
+          "apiserverProfile": {
+            "visibility": "Public",
+            "url": "https://api.cluster.location.aroapp.io:6443/",
+            "ip": "1.2.3.4"
+          },
+          "ingressProfiles": [
+            {
+              "name": "default",
+              "visibility": "Public",
+              "ip": "1.2.3.4"
+            }
+          ]
+        }
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/Operations_List.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/Operations_List.json
new file mode 100644
index 000000000..39e48e8ee
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/examples/Operations_List.json
@@ -0,0 +1,21 @@
+{
+  "parameters": {
+    "api-version": "2022-09-04"
+  },
+  "responses": {
+    "200": {
+      "body": {
+        "value": [
+          {
+            "name": "Microsoft.RedHatOpenShift/openShiftClusters/read",
+            "display": {
+              "provider": "Azure Red Hat OpenShift",
+              "resource": "openShiftClusters",
+              "operation": "Read OpenShift cluster"
+            }
+          }
+        ]
+      }
+    }
+  }
+}
diff --git a/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/redhatopenshift.json b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/redhatopenshift.json
new file mode 100644
index 000000000..481c4827c
--- /dev/null
+++ b/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/stable/2022-09-04/redhatopenshift.json
@@ -0,0 +1,913 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "Azure Red Hat OpenShift Client",
+    "description": "Rest API for Azure Red Hat OpenShift 4",
+    "version": "2022-09-04"
+  },
+  "host": "management.azure.com",
+  "schemes": [
+    "https"
+  ],
+  "consumes": [
+    "application/json"
+  ],
+  "produces": [
+    "application/json"
+  ],
+  "paths": {
+    "/providers/Microsoft.RedHatOpenShift/operations": {
+      "get": {
+        "tags": [
+          "Operations"
+        ],
+        "summary": "Lists all of the available RP operations.",
+        "description": "The operation returns the RP operations.",
+        "operationId": "Operations_List",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OperationList"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-examples": {
+          "Lists all of the available RP operations.": {
+            "$ref": "./examples/Operations_List.json"
+          }
+        },
+        "x-ms-pageable": {
+          "nextLinkName": "nextLink"
+        }
+      }
+    },
+    "/subscriptions/{subscriptionId}/providers/Microsoft.RedHatOpenShift/openShiftClusters": {
+      "get": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Lists OpenShift clusters in the specified subscription.",
+        "description": "The operation returns properties of each OpenShift cluster.",
+        "operationId": "OpenShiftClusters_List",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftClusterList"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-examples": {
+          "Lists OpenShift clusters in the specified subscription.": {
+            "$ref": "./examples/OpenShiftClusters_List.json"
+          }
+        },
+        "x-ms-pageable": {
+          "nextLinkName": "nextLink"
+        }
+      }
+    },
+    "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.RedHatOpenShift/openShiftClusters": {
+      "get": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Lists OpenShift clusters in the specified subscription and resource group.",
+        "description": "The operation returns properties of each OpenShift cluster.",
+        "operationId": "OpenShiftClusters_ListByResourceGroup",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftClusterList"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-examples": {
+          "Lists OpenShift clusters in the specified subscription and resource group.": {
+            "$ref": "./examples/OpenShiftClusters_ListByResourceGroup.json"
+          }
+        },
+        "x-ms-pageable": {
+          "nextLinkName": "nextLink"
+        }
+      }
+    },
+    "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.RedHatOpenShift/openShiftClusters/{resourceName}": {
+      "get": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Gets a OpenShift cluster with the specified subscription, resource group and resource name.",
+        "description": "The operation returns properties of a OpenShift cluster.",
+        "operationId": "OpenShiftClusters_Get",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          },
+          {
+            "name": "resourceName",
+            "in": "path",
+            "description": "The name of the OpenShift cluster resource.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftCluster"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-examples": {
+          "Gets a OpenShift cluster with the specified subscription, resource group and resource name.": {
+            "$ref": "./examples/OpenShiftClusters_Get.json"
+          }
+        }
+      },
+      "put": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Creates or updates a OpenShift cluster with the specified subscription, resource group and resource name.",
+        "description": "The operation returns properties of a OpenShift cluster.",
+        "operationId": "OpenShiftClusters_CreateOrUpdate",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          },
+          {
+            "name": "resourceName",
+            "in": "path",
+            "description": "The name of the OpenShift cluster resource.",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "parameters",
+            "in": "body",
+            "description": "The OpenShift cluster resource.",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/OpenShiftCluster"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftCluster"
+            }
+          },
+          "201": {
+            "description": "Created",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftCluster"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-long-running-operation": true,
+        "x-ms-examples": {
+          "Creates or updates a OpenShift cluster with the specified subscription, resource group and resource name.": {
+            "$ref": "./examples/OpenShiftClusters_CreateOrUpdate.json"
+          }
+        }
+      },
+      "delete": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Deletes a OpenShift cluster with the specified subscription, resource group and resource name.",
+        "description": "The operation returns nothing.",
+        "operationId": "OpenShiftClusters_Delete",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          },
+          {
+            "name": "resourceName",
+            "in": "path",
+            "description": "The name of the OpenShift cluster resource.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "202": {
+            "description": "Accepted"
+          },
+          "204": {
+            "description": "No Content"
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-long-running-operation": true,
+        "x-ms-examples": {
+          "Deletes a OpenShift cluster with the specified subscription, resource group and resource name.": {
+            "$ref": "./examples/OpenShiftClusters_Delete.json"
+          }
+        }
+      },
+      "patch": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Creates or updates a OpenShift cluster with the specified subscription, resource group and resource name.",
+        "description": "The operation returns properties of a OpenShift cluster.",
+        "operationId": "OpenShiftClusters_Update",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          },
+          {
+            "name": "resourceName",
+            "in": "path",
+            "description": "The name of the OpenShift cluster resource.",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "parameters",
+            "in": "body",
+            "description": "The OpenShift cluster resource.",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/OpenShiftClusterUpdate"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftCluster"
+            }
+          },
+          "201": {
+            "description": "Created",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftCluster"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-long-running-operation": true,
+        "x-ms-examples": {
+          "Creates or updates a OpenShift cluster with the specified subscription, resource group and resource name.": {
+            "$ref": "./examples/OpenShiftClusters_Update.json"
+          }
+        }
+      }
+    },
+    "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.RedHatOpenShift/openShiftClusters/{resourceName}/listAdminCredentials": {
+      "post": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Lists admin kubeconfig of an OpenShift cluster with the specified subscription, resource group and resource name.",
+        "description": "The operation returns the admin kubeconfig.",
+        "operationId": "OpenShiftClusters_ListAdminCredentials",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          },
+          {
+            "name": "resourceName",
+            "in": "path",
+            "description": "The name of the OpenShift cluster resource.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftClusterAdminKubeconfig"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-examples": {
+          "Lists admin kubeconfig of an OpenShift cluster with the specified subscription, resource group and resource name.": {
+            "$ref": "./examples/OpenShiftClusters_ListAdminCredentials.json"
+          }
+        }
+      }
+    },
+    "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.RedHatOpenShift/openShiftClusters/{resourceName}/listCredentials": {
+      "post": {
+        "tags": [
+          "OpenShiftClusters"
+        ],
+        "summary": "Lists credentials of an OpenShift cluster with the specified subscription, resource group and resource name.",
+        "description": "The operation returns the credentials.",
+        "operationId": "OpenShiftClusters_ListCredentials",
+        "parameters": [
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ApiVersionParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/SubscriptionIdParameter"
+          },
+          {
+            "$ref": "../../../../../common-types/resource-management/v2/types.json#/parameters/ResourceGroupNameParameter"
+          },
+          {
+            "name": "resourceName",
+            "in": "path",
+            "description": "The name of the OpenShift cluster resource.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OpenShiftClusterCredentials"
+            }
+          },
+          "default": {
+            "description": "Error response describing why the operation failed.  If the resource doesn't exist, 404 (Not Found) is returned.  If any of the input parameters is wrong, 400 (Bad Request) is returned.",
+            "schema": {
+              "$ref": "#/definitions/CloudError"
+            }
+          }
+        },
+        "x-ms-examples": {
+          "Lists credentials of an OpenShift cluster with the specified subscription, resource group and resource name.": {
+            "$ref": "./examples/OpenShiftClusters_ListCredentials.json"
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "APIServerProfile": {
+      "description": "APIServerProfile represents an API server profile.",
+      "type": "object",
+      "properties": {
+        "visibility": {
+          "$ref": "#/definitions/Visibility",
+          "description": "API server visibility."
+        },
+        "url": {
+          "description": "The URL to access the cluster API server.",
+          "type": "string"
+        },
+        "ip": {
+          "description": "The IP of the cluster API server.",
+          "type": "string"
+        }
+      }
+    },
+    "CloudError": {
+      "description": "CloudError represents a cloud error.",
+      "type": "object",
+      "properties": {
+        "error": {
+          "$ref": "#/definitions/CloudErrorBody",
+          "description": "An error response from the service."
+        }
+      }
+    },
+    "CloudErrorBody": {
+      "description": "CloudErrorBody represents the body of a cloud error.",
+      "type": "object",
+      "properties": {
+        "code": {
+          "description": "An identifier for the error. Codes are invariant and are intended to be consumed programmatically.",
+          "type": "string"
+        },
+        "message": {
+          "description": "A message describing the error, intended to be suitable for display in a user interface.",
+          "type": "string"
+        },
+        "target": {
+          "description": "The target of the particular error. For example, the name of the property in error.",
+          "type": "string"
+        },
+        "details": {
+          "description": "A list of additional details about the error.",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/CloudErrorBody"
+          },
+          "x-ms-identifiers": []
+        }
+      }
+    },
+    "ClusterProfile": {
+      "description": "ClusterProfile represents a cluster profile.",
+      "type": "object",
+      "properties": {
+        "pullSecret": {
+          "description": "The pull secret for the cluster.",
+          "type": "string"
+        },
+        "domain": {
+          "description": "The domain for the cluster.",
+          "type": "string"
+        },
+        "version": {
+          "description": "The version of the cluster.",
+          "type": "string"
+        },
+        "resourceGroupId": {
+          "description": "The ID of the cluster resource group.",
+          "type": "string"
+        },
+        "fipsValidatedModules": {
+          "$ref": "#/definitions/FipsValidatedModules",
+          "description": "If FIPS validated crypto modules are used"
+        }
+      }
+    },
+    "ConsoleProfile": {
+      "description": "ConsoleProfile represents a console profile.",
+      "type": "object",
+      "properties": {
+        "url": {
+          "description": "The URL to access the cluster console.",
+          "type": "string"
+        }
+      }
+    },
+    "Display": {
+      "description": "Display represents the display details of an operation.",
+      "type": "object",
+      "properties": {
+        "provider": {
+          "description": "Friendly name of the resource provider.",
+          "type": "string"
+        },
+        "resource": {
+          "description": "Resource type on which the operation is performed.",
+          "type": "string"
+        },
+        "operation": {
+          "description": "Operation type: read, write, delete, listKeys/action, etc.",
+          "type": "string"
+        },
+        "description": {
+          "description": "Friendly name of the operation.",
+          "type": "string"
+        }
+      }
+    },
+    "EncryptionAtHost": {
+      "description": "EncryptionAtHost represents encryption at host state",
+      "enum": [
+        "Disabled",
+        "Enabled"
+      ],
+      "type": "string",
+      "x-ms-enum": {
+        "name": "EncryptionAtHost",
+        "modelAsString": true
+      }
+    },
+    "FipsValidatedModules": {
+      "description": "FipsValidatedModules determines if FIPS is used.",
+      "enum": [
+        "Disabled",
+        "Enabled"
+      ],
+      "type": "string",
+      "x-ms-enum": {
+        "name": "FipsValidatedModules",
+        "modelAsString": true
+      }
+    },
+    "IngressProfile": {
+      "description": "IngressProfile represents an ingress profile.",
+      "type": "object",
+      "properties": {
+        "name": {
+          "description": "The ingress profile name.",
+          "type": "string"
+        },
+        "visibility": {
+          "$ref": "#/definitions/Visibility",
+          "description": "Ingress visibility."
+        },
+        "ip": {
+          "description": "The IP of the ingress.",
+          "type": "string"
+        }
+      }
+    },
+    "MasterProfile": {
+      "description": "MasterProfile represents a master profile.",
+      "type": "object",
+      "properties": {
+        "vmSize": {
+          "$ref": "#/definitions/VMSize",
+          "description": "The size of the master VMs."
+        },
+        "subnetId": {
+          "description": "The Azure resource ID of the master subnet.",
+          "type": "string"
+        },
+        "encryptionAtHost": {
+          "$ref": "#/definitions/EncryptionAtHost",
+          "description": "Whether master virtual machines are encrypted at host."
+        },
+        "diskEncryptionSetId": {
+          "description": "The resource ID of an associated DiskEncryptionSet, if applicable.",
+          "type": "string"
+        }
+      }
+    },
+    "NetworkProfile": {
+      "description": "NetworkProfile represents a network profile.",
+      "type": "object",
+      "properties": {
+        "podCidr": {
+          "description": "The CIDR used for OpenShift/Kubernetes Pods.",
+          "type": "string"
+        },
+        "serviceCidr": {
+          "description": "The CIDR used for OpenShift/Kubernetes Services.",
+          "type": "string"
+        }
+      }
+    },
+    "OpenShiftCluster": {
+      "description": "OpenShiftCluster represents an Azure Red Hat OpenShift cluster.",
+      "type": "object",
+      "allOf": [
+        {
+          "$ref": "../../../../../common-types/resource-management/v2/types.json#/definitions/TrackedResource"
+        }
+      ],
+      "properties": {
+        "properties": {
+          "$ref": "#/definitions/OpenShiftClusterProperties",
+          "description": "The cluster properties.",
+          "x-ms-client-flatten": true
+        },
+        "systemData": {
+          "$ref": "../../../../../common-types/resource-management/v2/types.json#/definitions/systemData",
+          "description": "The system meta data relating to this resource.",
+          "readOnly": true
+        }
+      }
+    },
+    "OpenShiftClusterAdminKubeconfig": {
+      "description": "OpenShiftClusterAdminKubeconfig represents an OpenShift cluster's admin kubeconfig.",
+      "type": "object",
+      "properties": {
+        "kubeconfig": {
+          "description": "The base64-encoded kubeconfig file.",
+          "type": "string",
+          "x-ms-secret": true
+        }
+      }
+    },
+    "OpenShiftClusterCredentials": {
+      "description": "OpenShiftClusterCredentials represents an OpenShift cluster's credentials.",
+      "type": "object",
+      "properties": {
+        "kubeadminUsername": {
+          "description": "The username for the kubeadmin user.",
+          "type": "string"
+        },
+        "kubeadminPassword": {
+          "description": "The password for the kubeadmin user.",
+          "type": "string",
+          "x-ms-secret": true
+        }
+      }
+    },
+    "OpenShiftClusterList": {
+      "description": "OpenShiftClusterList represents a list of OpenShift clusters.",
+      "type": "object",
+      "properties": {
+        "value": {
+          "description": "The list of OpenShift clusters.",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/OpenShiftCluster"
+          },
+          "x-ms-identifiers": []
+        },
+        "nextLink": {
+          "description": "The link used to get the next page of operations.",
+          "type": "string"
+        }
+      }
+    },
+    "OpenShiftClusterProperties": {
+      "description": "OpenShiftClusterProperties represents an OpenShift cluster's properties.",
+      "type": "object",
+      "properties": {
+        "provisioningState": {
+          "$ref": "#/definitions/ProvisioningState",
+          "description": "The cluster provisioning state."
+        },
+        "clusterProfile": {
+          "$ref": "#/definitions/ClusterProfile",
+          "description": "The cluster profile."
+        },
+        "consoleProfile": {
+          "$ref": "#/definitions/ConsoleProfile",
+          "description": "The console profile."
+        },
+        "servicePrincipalProfile": {
+          "$ref": "#/definitions/ServicePrincipalProfile",
+          "description": "The cluster service principal profile."
+        },
+        "networkProfile": {
+          "$ref": "#/definitions/NetworkProfile",
+          "description": "The cluster network profile."
+        },
+        "masterProfile": {
+          "$ref": "#/definitions/MasterProfile",
+          "description": "The cluster master profile."
+        },
+        "workerProfiles": {
+          "description": "The cluster worker profiles.",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/WorkerProfile"
+          },
+          "x-ms-identifiers": []
+        },
+        "apiserverProfile": {
+          "$ref": "#/definitions/APIServerProfile",
+          "description": "The cluster API server profile."
+        },
+        "ingressProfiles": {
+          "description": "The cluster ingress profiles.",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/IngressProfile"
+          },
+          "x-ms-identifiers": []
+        }
+      }
+    },
+    "OpenShiftClusterUpdate": {
+      "description": "OpenShiftCluster represents an Azure Red Hat OpenShift cluster.",
+      "type": "object",
+      "properties": {
+        "tags": {
+          "$ref": "#/definitions/Tags",
+          "description": "The resource tags."
+        },
+        "properties": {
+          "$ref": "#/definitions/OpenShiftClusterProperties",
+          "description": "The cluster properties.",
+          "x-ms-client-flatten": true
+        },
+        "systemData": {
+          "$ref": "../../../../../common-types/resource-management/v2/types.json#/definitions/systemData",
+          "description": "The system meta data relating to this resource.",
+          "readOnly": true
+        }
+      }
+    },
+    "Operation": {
+      "description": "Operation represents an RP operation.",
+      "type": "object",
+      "properties": {
+        "name": {
+          "description": "Operation name: {provider}/{resource}/{operation}.",
+          "type": "string"
+        },
+        "display": {
+          "$ref": "#/definitions/Display",
+          "description": "The object that describes the operation."
+        },
+        "origin": {
+          "description": "Sources of requests to this operation.  Comma separated list with valid values user or system, e.g. \"user,system\".",
+          "type": "string"
+        }
+      }
+    },
+    "OperationList": {
+      "description": "OperationList represents an RP operation list.",
+      "type": "object",
+      "properties": {
+        "value": {
+          "description": "List of operations supported by the resource provider.",
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/Operation"
+          },
+          "x-ms-identifiers": []
+        },
+        "nextLink": {
+          "description": "The link used to get the next page of operations.",
+          "type": "string"
+        }
+      }
+    },
+    "ProvisioningState": {
+      "description": "ProvisioningState represents a provisioning state.",
+      "enum": [
+        "AdminUpdating",
+        "Creating",
+        "Deleting",
+        "Failed",
+        "Succeeded",
+        "Updating"
+      ],
+      "type": "string"
+    },
+    "ServicePrincipalProfile": {
+      "description": "ServicePrincipalProfile represents a service principal profile.",
+      "type": "object",
+      "properties": {
+        "clientId": {
+          "description": "The client ID used for the cluster.",
+          "type": "string"
+        },
+        "clientSecret": {
+          "description": "The client secret used for the cluster.",
+          "type": "string"
+        }
+      }
+    },
+    "Tags": {
+      "description": "Tags represents an OpenShift cluster's tags.",
+      "type": "object",
+      "additionalProperties": {
+        "type": "string"
+      }
+    },
+    "VMSize": {
+      "description": "VM size availability varies by region.\nIf a node contains insufficient compute resources (memory, cpu, etc.), pods might fail to run correctly.\nFor more details on restricted VM sizes, see: https://docs.microsoft.com/en-us/azure/openshift/support-policies-v4#supported-virtual-machine-sizes",
+      "type": "string"
+    },
+    "Visibility": {
+      "description": "Visibility represents visibility.",
+      "enum": [
+        "Private",
+        "Public"
+      ],
+      "type": "string",
+      "x-ms-enum": {
+        "name": "Visibility",
+        "modelAsString": true
+      }
+    },
+    "WorkerProfile": {
+      "description": "WorkerProfile represents a worker profile.",
+      "type": "object",
+      "properties": {
+        "name": {
+          "description": "The worker profile name.",
+          "type": "string"
+        },
+        "vmSize": {
+          "$ref": "#/definitions/VMSize",
+          "description": "The size of the worker VMs."
+        },
+        "diskSizeGB": {
+          "description": "The disk size of the worker VMs.",
+          "type": "integer"
+        },
+        "subnetId": {
+          "description": "The Azure resource ID of the worker subnet.",
+          "type": "string"
+        },
+        "count": {
+          "description": "The number of worker VMs.",
+          "type": "integer"
+        },
+        "encryptionAtHost": {
+          "$ref": "#/definitions/EncryptionAtHost",
+          "description": "Whether master virtual machines are encrypted at host."
+        },
+        "diskEncryptionSetId": {
+          "description": "The resource ID of an associated DiskEncryptionSet, if applicable.",
+          "type": "string"
+        }
+      }
+    }
+  },
+  "securityDefinitions": {
+    "azure_auth": {
+      "type": "oauth2",
+      "description": "Azure Active Directory OAuth2 Flow",
+      "flow": "implicit",
+      "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize",
+      "scopes": {
+        "user_impersonation": "impersonate your user account"
+      }
+    }
+  },
+  "security": [
+    {
+      "azure_auth": [
+        "user_impersonation"
+      ]
+    }
+  ]
+}


Test patch:
diff --git a/pkg/api/v20220904/openshiftcluster_validatestatic.go b/pkg/api/v20220904/openshiftcluster_validatestatic.go
new file mode 100644
index 000000000..14ff66d6b
--- /dev/null
+++ b/pkg/api/v20220904/openshiftcluster_validatestatic.go
@@ -0,0 +1,364 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/Azure/go-autorest/autorest/azure"
+	"github.com/gofrs/uuid"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/api/validate"
+	"github.com/Azure/ARO-RP/pkg/util/immutable"
+	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
+	"github.com/Azure/ARO-RP/pkg/util/subnet"
+	"github.com/Azure/ARO-RP/pkg/util/version"
+)
+
+type openShiftClusterStaticValidator struct {
+	location            string
+	domain              string
+	requireD2sV3Workers bool
+	resourceID          string
+
+	r azure.Resource
+}
+
+// Validate validates an OpenShift cluster
+func (sv *openShiftClusterStaticValidator) Static(_oc interface{}, _current *api.OpenShiftCluster) error {
+	oc := _oc.(*OpenShiftCluster)
+
+	var current *OpenShiftCluster
+	if _current != nil {
+		current = (&openShiftClusterConverter{}).ToExternal(_current).(*OpenShiftCluster)
+	}
+
+	var err error
+	sv.r, err = azure.ParseResourceID(sv.resourceID)
+	if err != nil {
+		return err
+	}
+
+	err = sv.validate(oc, current == nil)
+	if err != nil {
+		return err
+	}
+
+	if current == nil {
+		return nil
+	}
+
+	return sv.validateDelta(oc, current)
+}
+
+func (sv *openShiftClusterStaticValidator) validate(oc *OpenShiftCluster, isCreate bool) error {
+	if !strings.EqualFold(oc.ID, sv.resourceID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeMismatchingResourceID, "id", "The provided resource ID '%s' did not match the name in the Url '%s'.", oc.ID, sv.resourceID)
+	}
+	if !strings.EqualFold(oc.Name, sv.r.ResourceName) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeMismatchingResourceName, "name", "The provided resource name '%s' did not match the name in the Url '%s'.", oc.Name, sv.r.ResourceName)
+	}
+	if !strings.EqualFold(oc.Type, resourceProviderNamespace+"/"+resourceType) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeMismatchingResourceType, "type", "The provided resource type '%s' did not match the name in the Url '%s'.", oc.Type, resourceProviderNamespace+"/"+resourceType)
+	}
+	if !strings.EqualFold(oc.Location, sv.location) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "location", "The provided location '%s' is invalid.", oc.Location)
+	}
+
+	// TODO: remove the VM name validation after https://bugzilla.redhat.com/show_bug.cgi?id=2093044 is resolved
+	if isCreate {
+		if !validate.OpenShiftClusterNameLength(oc.Name, oc.Location) {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "name", "The provided cluster name '%s' exceeds the maximum cluster name length of '%d'.", oc.Name, validate.MaxClusterNameLength)
+		}
+	}
+
+	return sv.validateProperties("properties", &oc.Properties, isCreate)
+}
+
+func (sv *openShiftClusterStaticValidator) validateProperties(path string, p *OpenShiftClusterProperties, isCreate bool) error {
+	switch p.ProvisioningState {
+	case ProvisioningStateCreating, ProvisioningStateUpdating,
+		ProvisioningStateAdminUpdating, ProvisioningStateDeleting,
+		ProvisioningStateSucceeded, ProvisioningStateFailed:
+	default:
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".provisioningState", "The provided provisioning state '%s' is invalid.", p.ProvisioningState)
+	}
+	if err := sv.validateClusterProfile(path+".clusterProfile", &p.ClusterProfile, isCreate); err != nil {
+		return err
+	}
+	if err := sv.validateConsoleProfile(path+".consoleProfile", &p.ConsoleProfile); err != nil {
+		return err
+	}
+	if err := sv.validateServicePrincipalProfile(path+".servicePrincipalProfile", &p.ServicePrincipalProfile); err != nil {
+		return err
+	}
+	if err := sv.validateNetworkProfile(path+".networkProfile", &p.NetworkProfile); err != nil {
+		return err
+	}
+	if err := sv.validateMasterProfile(path+".masterProfile", &p.MasterProfile); err != nil {
+		return err
+	}
+	if err := sv.validateAPIServerProfile(path+".apiserverProfile", &p.APIServerProfile); err != nil {
+		return err
+	}
+
+	if isCreate {
+		if len(p.WorkerProfiles) != 1 {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".workerProfiles", "There should be exactly one worker profile.")
+		}
+		if err := sv.validateWorkerProfile(path+".workerProfiles['"+p.WorkerProfiles[0].Name+"']", &p.WorkerProfiles[0], &p.MasterProfile); err != nil {
+			return err
+		}
+
+		if len(p.IngressProfiles) != 1 {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".ingressProfiles", "There should be exactly one ingress profile.")
+		}
+		if err := sv.validateIngressProfile(path+".ingressProfiles['"+p.IngressProfiles[0].Name+"']", &p.IngressProfiles[0]); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateClusterProfile(path string, cp *ClusterProfile, isCreate bool) error {
+	if pullsecret.Validate(cp.PullSecret) != nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".pullSecret", "The provided pull secret is invalid.")
+	}
+	if isCreate {
+		if !validate.RxDomainName.MatchString(cp.Domain) {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".domain", "The provided domain '%s' is invalid.", cp.Domain)
+		}
+	} else {
+		// We currently do not allow domains with a digit as a first charecter,
+		// for new clusters, but we already have some existing clusters with
+		// domains like this and we need to allow customers to update them.
+		if !validate.RxDomainNameRFC1123.MatchString(cp.Domain) {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".domain", "The provided domain '%s' is invalid.", cp.Domain)
+		}
+	}
+	// domain ends .aroapp.io, but doesn't end .<rp-location>.aroapp.io
+	if strings.HasSuffix(cp.Domain, "."+strings.SplitN(sv.domain, ".", 2)[1]) &&
+		!strings.HasSuffix(cp.Domain, "."+sv.domain) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".domain", "The provided domain '%s' is invalid.", cp.Domain)
+	}
+	// domain is of form multiple.names.<rp-location>.aroapp.io
+	if strings.HasSuffix(cp.Domain, "."+sv.domain) &&
+		strings.ContainsRune(strings.TrimSuffix(cp.Domain, "."+sv.domain), '.') {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".domain", "The provided domain '%s' is invalid.", cp.Domain)
+	}
+
+	if isCreate && cp.Version != version.InstallStream.Version.String() {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".version", "The provided version '%s' is invalid.", cp.Version)
+	}
+
+	if !validate.RxResourceGroupID.MatchString(cp.ResourceGroupID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".resourceGroupId", "The provided resource group '%s' is invalid.", cp.ResourceGroupID)
+	}
+	if strings.Split(cp.ResourceGroupID, "/")[2] != sv.r.SubscriptionID {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".resourceGroupId", "The provided resource group '%s' is invalid: must be in same subscription as cluster.", cp.ResourceGroupID)
+	}
+	if strings.EqualFold(cp.ResourceGroupID, fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", sv.r.SubscriptionID, sv.r.ResourceGroup)) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".resourceGroupId", "The provided resource group '%s' is invalid: must be different from resourceGroup of the OpenShift cluster object.", cp.ResourceGroupID)
+	}
+
+	switch cp.FipsValidatedModules {
+	case FipsValidatedModulesDisabled, FipsValidatedModulesEnabled:
+	default:
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".fipsValidatedModules", "The provided value '%s' is invalid.", cp.FipsValidatedModules)
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateConsoleProfile(path string, cp *ConsoleProfile) error {
+	if cp.URL != "" {
+		if _, err := url.Parse(cp.URL); err != nil {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".url", "The provided console URL '%s' is invalid.", cp.URL)
+		}
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
+	_, err := uuid.FromString(spp.ClientID)
+	if err != nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
+	}
+	if spp.ClientSecret == "" {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientSecret", "The provided client secret is invalid.")
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateNetworkProfile(path string, np *NetworkProfile) error {
+	_, pod, err := net.ParseCIDR(np.PodCIDR)
+	if err != nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".podCidr", "The provided pod CIDR '%s' is invalid: '%s'.", np.PodCIDR, err)
+	}
+	if pod.IP.To4() == nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".podCidr", "The provided pod CIDR '%s' is invalid: must be IPv4.", np.PodCIDR)
+	}
+	{
+		ones, _ := pod.Mask.Size()
+		if ones > 18 {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".podCidr", "The provided vnet CIDR '%s' is invalid: must be /18 or larger.", np.PodCIDR)
+		}
+	}
+	_, service, err := net.ParseCIDR(np.ServiceCIDR)
+	if err != nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".serviceCidr", "The provided service CIDR '%s' is invalid: '%s'.", np.ServiceCIDR, err)
+	}
+	if service.IP.To4() == nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".serviceCidr", "The provided service CIDR '%s' is invalid: must be IPv4.", np.ServiceCIDR)
+	}
+	{
+		ones, _ := service.Mask.Size()
+		if ones > 22 {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".serviceCidr", "The provided vnet CIDR '%s' is invalid: must be /22 or larger.", np.ServiceCIDR)
+		}
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateMasterProfile(path string, mp *MasterProfile) error {
+	if !validate.VMSizeIsValid(api.VMSize(mp.VMSize), sv.requireD2sV3Workers, true) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".vmSize", "The provided master VM size '%s' is invalid.", mp.VMSize)
+	}
+	if !validate.RxSubnetID.MatchString(mp.SubnetID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".subnetId", "The provided master VM subnet '%s' is invalid.", mp.SubnetID)
+	}
+	sr, err := azure.ParseResourceID(mp.SubnetID)
+	if err != nil {
+		return err
+	}
+	if sr.SubscriptionID != sv.r.SubscriptionID {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".subnetId", "The provided master VM subnet '%s' is invalid: must be in same subscription as cluster.", mp.SubnetID)
+	}
+	switch mp.EncryptionAtHost {
+	case EncryptionAtHostDisabled, EncryptionAtHostEnabled:
+	default:
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".encryptionAtHost", "The provided value '%s' is invalid.", mp.EncryptionAtHost)
+	}
+	if mp.DiskEncryptionSetID != "" {
+		if !validate.RxDiskEncryptionSetID.MatchString(mp.DiskEncryptionSetID) {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".diskEncryptionSetId", "The provided master disk encryption set '%s' is invalid.", mp.DiskEncryptionSetID)
+		}
+		desr, err := azure.ParseResourceID(mp.DiskEncryptionSetID)
+		if err != nil {
+			return err
+		}
+		if desr.SubscriptionID != sv.r.SubscriptionID {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".diskEncryptionSetId", "The provided master disk encryption set '%s' is invalid: must be in same subscription as cluster.", mp.DiskEncryptionSetID)
+		}
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateWorkerProfile(path string, wp *WorkerProfile, mp *MasterProfile) error {
+	if wp.Name != "worker" {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".name", "The provided worker name '%s' is invalid.", wp.Name)
+	}
+	if !validate.VMSizeIsValid(api.VMSize(wp.VMSize), sv.requireD2sV3Workers, false) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".vmSize", "The provided worker VM size '%s' is invalid.", wp.VMSize)
+	}
+	if !validate.DiskSizeIsValid(wp.DiskSizeGB) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".diskSizeGB", "The provided worker disk size '%d' is invalid.", wp.DiskSizeGB)
+	}
+	if !validate.RxSubnetID.MatchString(wp.SubnetID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".subnetId", "The provided worker VM subnet '%s' is invalid.", wp.SubnetID)
+	}
+	switch wp.EncryptionAtHost {
+	case EncryptionAtHostDisabled, EncryptionAtHostEnabled:
+	default:
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".encryptionAtHost", "The provided value '%s' is invalid.", wp.EncryptionAtHost)
+	}
+	workerVnetID, _, err := subnet.Split(wp.SubnetID)
+	if err != nil {
+		return err
+	}
+	masterVnetID, _, err := subnet.Split(mp.SubnetID)
+	if err != nil {
+		return err
+	}
+	if !strings.EqualFold(masterVnetID, workerVnetID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".subnetId", "The provided worker VM subnet '%s' is invalid: must be in the same vnet as master VM subnet '%s'.", wp.SubnetID, mp.SubnetID)
+	}
+	if strings.EqualFold(mp.SubnetID, wp.SubnetID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".subnetId", "The provided worker VM subnet '%s' is invalid: must be different to master VM subnet '%s'.", wp.SubnetID, mp.SubnetID)
+	}
+	if wp.Count < 2 || wp.Count > 50 {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".count", "The provided worker count '%d' is invalid.", wp.Count)
+	}
+	if !strings.EqualFold(mp.DiskEncryptionSetID, wp.DiskEncryptionSetID) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".subnetId", "The provided worker disk encryption set '%s' is invalid: must be the same as master disk encryption set '%s'.", wp.DiskEncryptionSetID, mp.DiskEncryptionSetID)
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateAPIServerProfile(path string, ap *APIServerProfile) error {
+	switch ap.Visibility {
+	case VisibilityPublic, VisibilityPrivate:
+	default:
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".visibility", "The provided visibility '%s' is invalid.", ap.Visibility)
+	}
+	if ap.URL != "" {
+		if _, err := url.Parse(ap.URL); err != nil {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".url", "The provided URL '%s' is invalid.", ap.URL)
+		}
+	}
+	if ap.IP != "" {
+		ip := net.ParseIP(ap.IP)
+		if ip == nil {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".ip", "The provided IP '%s' is invalid.", ap.IP)
+		}
+		if ip.To4() == nil {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".ip", "The provided IP '%s' is invalid: must be IPv4.", ap.IP)
+		}
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateIngressProfile(path string, p *IngressProfile) error {
+	if p.Name != "default" {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".name", "The provided ingress name '%s' is invalid.", p.Name)
+	}
+	switch p.Visibility {
+	case VisibilityPublic, VisibilityPrivate:
+	default:
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".visibility", "The provided visibility '%s' is invalid.", p.Visibility)
+	}
+	if p.IP != "" {
+		ip := net.ParseIP(p.IP)
+		if ip == nil {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".ip", "The provided IP '%s' is invalid.", p.IP)
+		}
+		if ip.To4() == nil {
+			return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".ip", "The provided IP '%s' is invalid: must be IPv4.", p.IP)
+		}
+	}
+
+	return nil
+}
+
+func (sv *openShiftClusterStaticValidator) validateDelta(oc, current *OpenShiftCluster) error {
+	err := immutable.Validate("", oc, 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/v20220904/openshiftcluster_validatestatic_test.go b/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
new file mode 100644
index 000000000..4edd8443f
--- /dev/null
+++ b/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
@@ -0,0 +1,986 @@
+package v20220904
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/Azure/go-autorest/autorest/azure"
+	"github.com/Azure/go-autorest/autorest/to"
+	"github.com/gofrs/uuid"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	apiValidate "github.com/Azure/ARO-RP/pkg/api/validate"
+	"github.com/Azure/ARO-RP/pkg/util/version"
+	"github.com/Azure/ARO-RP/test/validate"
+)
+
+type validateTest struct {
+	name                string
+	clusterName         *string
+	location            *string
+	current             func(oc *OpenShiftCluster)
+	modify              func(oc *OpenShiftCluster)
+	requireD2sV3Workers bool
+	wantErr             string
+}
+
+type testMode string
+
+const (
+	testModeCreate testMode = "Create"
+	testModeUpdate testMode = "Update"
+)
+
+var (
+	subscriptionID = "00000000-0000-0000-0000-000000000000"
+)
+
+func getResourceID(clusterName string) string {
+	return fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/microsoft.redhatopenshift/openshiftclusters/%s", subscriptionID, clusterName)
+}
+
+func validOpenShiftCluster(name, location string) *OpenShiftCluster {
+	timestamp, err := time.Parse(time.RFC3339, "2021-01-23T12:34:54.0000000Z")
+	if err != nil {
+		panic(err)
+	}
+
+	oc := &OpenShiftCluster{
+		ID:       getResourceID(name),
+		Name:     name,
+		Type:     "Microsoft.RedHatOpenShift/OpenShiftClusters",
+		Location: location,
+		Tags: Tags{
+			"key": "value",
+		},
+		SystemData: &SystemData{
+			CreatedBy:          "00000000-0000-0000-0000-000000000000",
+			CreatedByType:      CreatedByTypeApplication,
+			CreatedAt:          &timestamp,
+			LastModifiedBy:     "00000000-0000-0000-0000-000000000000",
+			LastModifiedByType: CreatedByTypeApplication,
+			LastModifiedAt:     &timestamp,
+		},
+		Properties: OpenShiftClusterProperties{
+			ProvisioningState: ProvisioningStateSucceeded,
+			ClusterProfile: ClusterProfile{
+				PullSecret:           `{"auths":{"registry.connect.redhat.com":{"auth":""},"registry.redhat.io":{"auth":""}}}`,
+				Domain:               "cluster.location.aroapp.io",
+				Version:              version.InstallStream.Version.String(),
+				ResourceGroupID:      fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", subscriptionID),
+				FipsValidatedModules: FipsValidatedModulesDisabled,
+			},
+			ConsoleProfile: ConsoleProfile{
+				URL: "https://console-openshift-console.apps.cluster.location.aroapp.io/",
+			},
+			ServicePrincipalProfile: ServicePrincipalProfile{
+				ClientSecret: "clientSecret",
+				ClientID:     "11111111-1111-1111-1111-111111111111",
+			},
+			NetworkProfile: NetworkProfile{
+				PodCIDR:     "10.128.0.0/14",
+				ServiceCIDR: "172.30.0.0/16",
+			},
+			MasterProfile: MasterProfile{
+				VMSize:           "Standard_D8s_v3",
+				EncryptionAtHost: EncryptionAtHostDisabled,
+				SubnetID:         fmt.Sprintf("/subscriptions/%s/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master", subscriptionID),
+			},
+			WorkerProfiles: []WorkerProfile{
+				{
+					Name:             "worker",
+					VMSize:           "Standard_D4s_v3",
+					EncryptionAtHost: EncryptionAtHostDisabled,
+					DiskSizeGB:       128,
+					SubnetID:         fmt.Sprintf("/subscriptions/%s/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/worker", subscriptionID),
+					Count:            3,
+				},
+			},
+			APIServerProfile: APIServerProfile{
+				Visibility: VisibilityPublic,
+				URL:        "https://api.cluster.location.aroapp.io:6443/",
+				IP:         "1.2.3.4",
+			},
+			IngressProfiles: []IngressProfile{
+				{
+					Name:       "default",
+					Visibility: VisibilityPublic,
+					IP:         "1.2.3.4",
+				},
+			},
+		},
+	}
+
+	return oc
+}
+
+func runTests(t *testing.T, mode testMode, tests []*validateTest) {
+	t.Run(string(mode), func(t *testing.T) {
+		for _, tt := range tests {
+			t.Run(tt.name, func(t *testing.T) {
+				// default values if not set
+				if tt.location == nil {
+					tt.location = to.StringPtr("location")
+				}
+
+				if tt.clusterName == nil {
+					tt.clusterName = to.StringPtr("resourceName")
+				}
+
+				v := &openShiftClusterStaticValidator{
+					location:            *tt.location,
+					domain:              "location.aroapp.io",
+					requireD2sV3Workers: tt.requireD2sV3Workers,
+					resourceID:          getResourceID(*tt.clusterName),
+					r: azure.Resource{
+						SubscriptionID: subscriptionID,
+						ResourceGroup:  "resourceGroup",
+						Provider:       "Microsoft.RedHatOpenShift",
+						ResourceType:   "openshiftClusters",
+						ResourceName:   *tt.clusterName,
+					},
+				}
+
+				validOCForTest := func() *OpenShiftCluster {
+					oc := validOpenShiftCluster(*tt.clusterName, *tt.location)
+					if tt.current != nil {
+						tt.current(oc)
+					}
+					return oc
+				}
+
+				oc := validOCForTest()
+				if tt.modify != nil {
+					tt.modify(oc)
+				}
+
+				var current *api.OpenShiftCluster
+				if mode == testModeUpdate {
+					current = &api.OpenShiftCluster{}
+					(&openShiftClusterConverter{}).ToInternal(validOCForTest(), current)
+				}
+
+				err := v.Static(oc, current)
+				if err == nil {
+					if tt.wantErr != "" {
+						t.Error(err)
+					}
+				} else {
+					if err.Error() != tt.wantErr {
+						t.Error(err)
+					}
+
+					cloudErr := err.(*api.CloudError)
+
+					if cloudErr.StatusCode != http.StatusBadRequest {
+						t.Error(cloudErr.StatusCode)
+					}
+					if cloudErr.Target == "" {
+						t.Error("target is required")
+					}
+
+					validate.CloudError(t, err)
+				}
+			})
+		}
+	})
+}
+
+func TestOpenShiftClusterStaticValidate(t *testing.T) {
+	clusterName19 := "19characters-aaaaaa"
+	clusterName30 := "thisis30characterslong-aaaaaa"
+	nonZonalRegion := "australiasoutheast"
+
+	commonTests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "id wrong",
+			modify: func(oc *OpenShiftCluster) {
+				oc.ID = "wrong"
+			},
+			wantErr: "400: MismatchingResourceID: id: The provided resource ID 'wrong' did not match the name in the Url '/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/microsoft.redhatopenshift/openshiftclusters/resourceName'.",
+		},
+		{
+			name: "name wrong",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Name = "wrong"
+			},
+			wantErr: "400: MismatchingResourceName: name: The provided resource name 'wrong' did not match the name in the Url 'resourceName'.",
+		},
+		{
+			name: "type wrong",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Type = "wrong"
+			},
+			wantErr: "400: MismatchingResourceType: type: The provided resource type 'wrong' did not match the name in the Url 'Microsoft.RedHatOpenShift/openShiftClusters'.",
+		},
+		{
+			name: "location invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Location = "invalid"
+			},
+			wantErr: "400: InvalidParameter: location: The provided location 'invalid' is invalid.",
+		},
+		{
+			name:        "valid - zonal regions can exceed max cluster name length",
+			clusterName: &clusterName30,
+		},
+	}
+
+	createTests := []*validateTest{
+		{
+			name:        "invalid - non-zonal regions cannot exceed max cluster name length on cluster create",
+			clusterName: &clusterName30,
+			location:    &nonZonalRegion,
+			wantErr:     fmt.Sprintf("400: InvalidParameter: name: The provided cluster name '%s' exceeds the maximum cluster name length of '%d'.", clusterName30, apiValidate.MaxClusterNameLength),
+		},
+		{
+			name:        "valid - non-zonal region less than max cluster name length",
+			clusterName: &clusterName19,
+			location:    &nonZonalRegion,
+		},
+	}
+
+	updateTests := []*validateTest{
+		{
+			name:        "valid - existing cluster names > max cluster name length still work on cluster update",
+			clusterName: &clusterName30,
+			location:    &nonZonalRegion,
+		},
+	}
+
+	runTests(t, testModeCreate, commonTests)
+	runTests(t, testModeUpdate, commonTests)
+	runTests(t, testModeCreate, createTests)
+	runTests(t, testModeUpdate, updateTests)
+}
+
+func TestOpenShiftClusterStaticValidateProperties(t *testing.T) {
+	commonTests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "provisioningState invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ProvisioningState = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.provisioningState: The provided provisioning state 'invalid' is invalid.",
+		},
+	}
+	createTests := []*validateTest{
+		{
+			name: "no workerProfiles invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles = nil
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles: There should be exactly one worker profile.",
+		},
+		{
+			name: "multiple workerProfiles invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles = []WorkerProfile{{}, {}}
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles: There should be exactly one worker profile.",
+		},
+	}
+
+	runTests(t, testModeCreate, createTests)
+	runTests(t, testModeCreate, commonTests)
+	runTests(t, testModeUpdate, commonTests)
+}
+
+func TestOpenShiftClusterStaticValidateClusterProfile(t *testing.T) {
+	commonTests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "pull secret not a map",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.PullSecret = "1"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.pullSecret: The provided pull secret is invalid.",
+		},
+		{
+			name: "pull secret invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.PullSecret = "{"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.pullSecret: The provided pull secret is invalid.",
+		},
+		{
+			name: "empty domain invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = ""
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.domain: The provided domain '' is invalid.",
+		},
+		{
+			name: "upper case domain invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = "BAD"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.domain: The provided domain 'BAD' is invalid.",
+		},
+		{
+			name: "domain invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = "!"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.domain: The provided domain '!' is invalid.",
+		},
+		{
+			name: "wrong location managed domain invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = "cluster.wronglocation.aroapp.io"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.domain: The provided domain 'cluster.wronglocation.aroapp.io' is invalid.",
+		},
+		{
+			name: "double part managed domain invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = "foo.bar.location.aroapp.io"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.domain: The provided domain 'foo.bar.location.aroapp.io' is invalid.",
+		},
+		{
+			name: "resourceGroupId invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.ResourceGroupID = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.resourceGroupId: The provided resource group 'invalid' is invalid.",
+		},
+		{
+			name: "cluster resource group subscriptionId not matching cluster subscriptionId",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.ResourceGroupID = "/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourcegroups/test-cluster"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.resourceGroupId: The provided resource group '/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourcegroups/test-cluster' is invalid: must be in same subscription as cluster.",
+		},
+		{
+			name: "cluster resourceGroup and external resourceGroup equal",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.ResourceGroupID = "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.resourceGroupId: The provided resource group '/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup' is invalid: must be different from resourceGroup of the OpenShift cluster object.",
+		},
+		{
+			name: "fips validated modules invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.FipsValidatedModules = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.fipsValidatedModules: The provided value 'invalid' is invalid.",
+		},
+		{
+			name: "fips validated modules empty",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.FipsValidatedModules = ""
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.fipsValidatedModules: The provided value '' is invalid.",
+		},
+	}
+
+	createTests := []*validateTest{
+		{
+			name: "empty pull secret valid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.PullSecret = ""
+			},
+		},
+		{
+			name: "version invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Version = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.version: The provided version 'invalid' is invalid.",
+		},
+		{
+			name: "leading digit domain invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = "4k7f9clk"
+			},
+			wantErr: "400: InvalidParameter: properties.clusterProfile.domain: The provided domain '4k7f9clk' is invalid.",
+		},
+	}
+
+	updateTests := []*validateTest{
+		{
+			name: "leading digit domain valid",
+			current: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.Domain = "4k7f9clk"
+			},
+		},
+	}
+
+	runTests(t, testModeCreate, createTests)
+	runTests(t, testModeCreate, commonTests)
+	runTests(t, testModeUpdate, updateTests)
+	runTests(t, testModeUpdate, commonTests)
+}
+
+func TestOpenShiftClusterStaticValidateConsoleProfile(t *testing.T) {
+	commonTests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "console url invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ConsoleProfile.URL = "\x00"
+			},
+			wantErr: "400: InvalidParameter: properties.consoleProfile.url: The provided console URL '\x00' is invalid.",
+		},
+	}
+
+	createTests := []*validateTest{
+		{
+			name: "empty console url valid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ConsoleProfile.URL = ""
+			},
+		},
+	}
+
+	runTests(t, testModeCreate, createTests)
+	runTests(t, testModeCreate, commonTests)
+	runTests(t, testModeUpdate, commonTests)
+}
+
+func TestOpenShiftClusterStaticValidateServicePrincipalProfile(t *testing.T) {
+	tests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "clientID invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ServicePrincipalProfile.ClientID = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientId: The provided client ID 'invalid' is invalid.",
+		},
+		{
+			name: "empty clientSecret invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ServicePrincipalProfile.ClientSecret = ""
+			},
+			wantErr: "400: InvalidParameter: properties.servicePrincipalProfile.clientSecret: The provided client secret is invalid.",
+		},
+	}
+
+	runTests(t, testModeCreate, tests)
+	runTests(t, testModeUpdate, tests)
+}
+
+func TestOpenShiftClusterStaticValidateNetworkProfile(t *testing.T) {
+	tests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "podCidr invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.NetworkProfile.PodCIDR = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.networkProfile.podCidr: The provided pod CIDR 'invalid' is invalid: 'invalid CIDR address: invalid'.",
+		},
+		{
+			name: "ipv6 podCidr invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.NetworkProfile.PodCIDR = "::0/0"
+			},
+			wantErr: "400: InvalidParameter: properties.networkProfile.podCidr: The provided pod CIDR '::0/0' is invalid: must be IPv4.",
+		},
+		{
+			name: "serviceCidr invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.NetworkProfile.ServiceCIDR = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.networkProfile.serviceCidr: The provided service CIDR 'invalid' is invalid: 'invalid CIDR address: invalid'.",
+		},
+		{
+			name: "ipv6 serviceCidr invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.NetworkProfile.ServiceCIDR = "::0/0"
+			},
+			wantErr: "400: InvalidParameter: properties.networkProfile.serviceCidr: The provided service CIDR '::0/0' is invalid: must be IPv4.",
+		},
+		{
+			name: "podCidr too small",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.NetworkProfile.PodCIDR = "10.0.0.0/19"
+			},
+			wantErr: "400: InvalidParameter: properties.networkProfile.podCidr: The provided vnet CIDR '10.0.0.0/19' is invalid: must be /18 or larger.",
+		},
+		{
+			name: "serviceCidr too small",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.NetworkProfile.ServiceCIDR = "10.0.0.0/23"
+			},
+			wantErr: "400: InvalidParameter: properties.networkProfile.serviceCidr: The provided vnet CIDR '10.0.0.0/23' is invalid: must be /22 or larger.",
+		},
+	}
+
+	runTests(t, testModeCreate, tests)
+	runTests(t, testModeUpdate, tests)
+}
+
+func TestOpenShiftClusterStaticValidateMasterProfile(t *testing.T) {
+	tests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "vmSize unsupported",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.VMSize = "Standard_D2s_v3"
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.vmSize: The provided master VM size 'Standard_D2s_v3' is invalid.",
+		},
+		{
+			name: "subnetId invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.SubnetID = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.subnetId: The provided master VM subnet 'invalid' is invalid.",
+		},
+		{
+			name: "subnet subscriptionId not matching cluster subscriptionId",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.SubnetID = "/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourcegroups/test-vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master"
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.subnetId: The provided master VM subnet '/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourcegroups/test-vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master' is invalid: must be in same subscription as cluster.",
+		},
+		{
+			name: "disk encryption set is invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.DiskEncryptionSetID = "invalid"
+				oc.Properties.WorkerProfiles[0].DiskEncryptionSetID = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.diskEncryptionSetId: The provided master disk encryption set 'invalid' is invalid.",
+		},
+		{
+			name: "disk encryption set not matching cluster subscriptionId",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.DiskEncryptionSetID = "/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourceGroups/fakeRG/providers/Microsoft.Compute/diskEncryptionSets/fakeDES1"
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.diskEncryptionSetId: The provided master disk encryption set '/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourceGroups/fakeRG/providers/Microsoft.Compute/diskEncryptionSets/fakeDES1' is invalid: must be in same subscription as cluster.",
+		},
+		{
+			name: "encryption at host invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.EncryptionAtHost = "Banana"
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.encryptionAtHost: The provided value 'Banana' is invalid.",
+		},
+		{
+			name: "encryption at host empty",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.EncryptionAtHost = ""
+			},
+			wantErr: "400: InvalidParameter: properties.masterProfile.encryptionAtHost: The provided value '' is invalid.",
+		},
+	}
+
+	createTests := []*validateTest{
+		{
+			name: "disk encryption set is valid",
+			modify: func(oc *OpenShiftCluster) {
+				desID := fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster/providers/Microsoft.Compute/diskEncryptionSets/test-disk-encryption-set", subscriptionID)
+				oc.Properties.MasterProfile.DiskEncryptionSetID = desID
+				oc.Properties.WorkerProfiles[0].DiskEncryptionSetID = desID
+			},
+		},
+	}
+
+	runTests(t, testModeCreate, createTests)
+	runTests(t, testModeCreate, tests)
+	runTests(t, testModeUpdate, tests)
+}
+
+func TestOpenShiftClusterStaticValidateWorkerProfile(t *testing.T) {
+	tests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "name invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].Name = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['invalid'].name: The provided worker name 'invalid' is invalid.",
+		},
+		{
+			name: "vmSize invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].VMSize = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].vmSize: The provided worker VM size 'invalid' is invalid.",
+		},
+		{
+			name: "vmSize too small (prod)",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].VMSize = "Standard_D2s_v3"
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].vmSize: The provided worker VM size 'Standard_D2s_v3' is invalid.",
+		},
+		{
+			name: "vmSize too big (dev)",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].VMSize = "Standard_D4s_v3"
+			},
+			requireD2sV3Workers: true,
+			wantErr:             "400: InvalidParameter: properties.workerProfiles['worker'].vmSize: The provided worker VM size 'Standard_D4s_v3' is invalid.",
+		},
+		{
+			name: "disk too small",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].DiskSizeGB = 127
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].diskSizeGB: The provided worker disk size '127' is invalid.",
+		},
+		{
+			name: "subnetId invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].SubnetID = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].subnetId: The provided worker VM subnet 'invalid' is invalid.",
+		},
+		{
+			name: "master and worker subnets not in same vnet",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].SubnetID = fmt.Sprintf("/subscriptions/%s/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/different-vnet/subnets/worker", subscriptionID)
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].subnetId: The provided worker VM subnet '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/different-vnet/subnets/worker' is invalid: must be in the same vnet as master VM subnet '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master'.",
+		},
+		{
+			name: "master and worker subnets not different",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].SubnetID = oc.Properties.MasterProfile.SubnetID
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].subnetId: The provided worker VM subnet '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master' is invalid: must be different to master VM subnet '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/master'.",
+		},
+		{
+			name: "count too small",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].Count = 1
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].count: The provided worker count '1' is invalid.",
+		},
+		{
+			name: "count too big",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].Count = 51
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].count: The provided worker count '51' is invalid.",
+		},
+		{
+			name: "disk encryption set not matching master disk encryption set",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.DiskEncryptionSetID = fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster/providers/Microsoft.Compute/diskEncryptionSets/test-disk-encryption-set", subscriptionID)
+				oc.Properties.WorkerProfiles[0].DiskEncryptionSetID = "/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourceGroups/fakeRG/providers/Microsoft.Compute/diskEncryptionSets/fakeDES1"
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].subnetId: The provided worker disk encryption set '/subscriptions/7a3036d1-60a1-4605-8a41-44955e050804/resourceGroups/fakeRG/providers/Microsoft.Compute/diskEncryptionSets/fakeDES1' is invalid: must be the same as master disk encryption set '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-cluster/providers/Microsoft.Compute/diskEncryptionSets/test-disk-encryption-set'.",
+		},
+		{
+			name: "encryption at host invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].EncryptionAtHost = "Banana"
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].encryptionAtHost: The provided value 'Banana' is invalid.",
+		},
+		{
+			name: "encryption at host empty",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].EncryptionAtHost = ""
+			},
+			wantErr: "400: InvalidParameter: properties.workerProfiles['worker'].encryptionAtHost: The provided value '' is invalid.",
+		},
+	}
+
+	// We do not perform this validation on update
+	runTests(t, testModeCreate, tests)
+}
+
+func TestOpenShiftClusterStaticValidateAPIServerProfile(t *testing.T) {
+	commonTests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "visibility invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.Visibility = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.apiserverProfile.visibility: The provided visibility 'invalid' is invalid.",
+		},
+		{
+			name: "url invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.URL = "\x00"
+			},
+			wantErr: "400: InvalidParameter: properties.apiserverProfile.url: The provided URL '\x00' is invalid.",
+		},
+		{
+			name: "ip invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.IP = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.apiserverProfile.ip: The provided IP 'invalid' is invalid.",
+		},
+		{
+			name: "ipv6 ip invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.IP = "::"
+			},
+			wantErr: "400: InvalidParameter: properties.apiserverProfile.ip: The provided IP '::' is invalid: must be IPv4.",
+		},
+	}
+
+	createTests := []*validateTest{
+		{
+			name: "empty url valid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.URL = ""
+			},
+		},
+		{
+			name: "empty ip valid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.IP = ""
+			},
+		},
+	}
+
+	runTests(t, testModeCreate, createTests)
+	runTests(t, testModeCreate, commonTests)
+	runTests(t, testModeUpdate, commonTests)
+}
+
+func TestOpenShiftClusterStaticValidateIngressProfile(t *testing.T) {
+	tests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name: "name invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.IngressProfiles[0].Name = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.ingressProfiles['invalid'].name: The provided ingress name 'invalid' is invalid.",
+		},
+		{
+			name: "visibility invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.IngressProfiles[0].Visibility = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.ingressProfiles['default'].visibility: The provided visibility 'invalid' is invalid.",
+		},
+		{
+			name: "ip invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.IngressProfiles[0].IP = "invalid"
+			},
+			wantErr: "400: InvalidParameter: properties.ingressProfiles['default'].ip: The provided IP 'invalid' is invalid.",
+		},
+		{
+			name: "ipv6 ip invalid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.IngressProfiles[0].IP = "::"
+			},
+			wantErr: "400: InvalidParameter: properties.ingressProfiles['default'].ip: The provided IP '::' is invalid: must be IPv4.",
+		},
+		{
+			name: "empty ip valid",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.IngressProfiles[0].IP = ""
+			},
+		},
+	}
+
+	// we don't validate this on update as all fields are immutable and will
+	// be validated with "mutable" flag
+	runTests(t, testModeCreate, tests)
+}
+
+func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
+	tests := []*validateTest{
+		{
+			name: "valid",
+		},
+		{
+			name:   "valid id case change",
+			modify: func(oc *OpenShiftCluster) { oc.ID = strings.ToUpper(oc.ID) },
+		},
+		{
+			name:   "valid name case change",
+			modify: func(oc *OpenShiftCluster) { oc.Name = strings.ToUpper(oc.Name) },
+		},
+		{
+			name:   "valid type case change",
+			modify: func(oc *OpenShiftCluster) { oc.Type = strings.ToUpper(oc.Type) },
+		},
+		{
+			name:    "location change",
+			modify:  func(oc *OpenShiftCluster) { oc.Location = strings.ToUpper(oc.Location) },
+			wantErr: "400: PropertyChangeNotAllowed: location: Changing property 'location' is not allowed.",
+		},
+		{
+			name:   "valid tags change",
+			modify: func(oc *OpenShiftCluster) { oc.Tags = Tags{"new": "value"} },
+		},
+		{
+			name:    "provisioningState change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.ProvisioningState = ProvisioningStateFailed },
+			wantErr: "400: PropertyChangeNotAllowed: properties.provisioningState: Changing property 'properties.provisioningState' is not allowed.",
+		},
+		{
+			name:    "console url change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.ConsoleProfile.URL = "invalid" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.consoleProfile.url: Changing property 'properties.consoleProfile.url' is not allowed.",
+		},
+		{
+			name:    "pull secret change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.ClusterProfile.PullSecret = `{"auths":{}}` },
+			wantErr: "400: PropertyChangeNotAllowed: properties.clusterProfile.pullSecret: Changing property 'properties.clusterProfile.pullSecret' is not allowed.",
+		},
+		{
+			name:    "domain change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.ClusterProfile.Domain = "invalid" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.clusterProfile.domain: Changing property 'properties.clusterProfile.domain' is not allowed.",
+		},
+		{
+			name:    "version change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.ClusterProfile.Version = "4.3.999" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.clusterProfile.version: Changing property 'properties.clusterProfile.version' is not allowed.",
+		},
+		{
+			name: "resource group change",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ClusterProfile.ResourceGroupID = oc.Properties.ClusterProfile.ResourceGroupID[:strings.LastIndexByte(oc.Properties.ClusterProfile.ResourceGroupID, '/')] + "/changed"
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.clusterProfile.resourceGroupId: Changing property 'properties.clusterProfile.resourceGroupId' is not allowed.",
+		},
+		{
+			name: "apiServer private change",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.APIServerProfile.Visibility = VisibilityPrivate
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.apiserverProfile.visibility: Changing property 'properties.apiserverProfile.visibility' is not allowed.",
+		},
+		{
+			name:    "apiServer url change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.APIServerProfile.URL = "invalid" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.apiserverProfile.url: Changing property 'properties.apiserverProfile.url' is not allowed.",
+		},
+		{
+			name:    "apiServer ip change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.APIServerProfile.IP = "2.3.4.5" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.apiserverProfile.ip: Changing property 'properties.apiserverProfile.ip' is not allowed.",
+		},
+		{
+			name: "ingress private change",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.IngressProfiles[0].Visibility = VisibilityPrivate
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.ingressProfiles['default'].visibility: Changing property 'properties.ingressProfiles['default'].visibility' is not allowed.",
+		},
+		{
+			name:    "ingress ip change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.IngressProfiles[0].IP = "2.3.4.5" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.ingressProfiles['default'].ip: Changing property 'properties.ingressProfiles['default'].ip' is not allowed.",
+		},
+		{
+			name: "clientId change",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+			},
+		},
+		{
+			name:   "clientSecret change",
+			modify: func(oc *OpenShiftCluster) { oc.Properties.ServicePrincipalProfile.ClientSecret = "invalid" },
+		},
+		{
+			name:    "podCidr change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.NetworkProfile.PodCIDR = "0.0.0.0/0" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.networkProfile.podCidr: Changing property 'properties.networkProfile.podCidr' is not allowed.",
+		},
+		{
+			name:    "serviceCidr change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.NetworkProfile.ServiceCIDR = "0.0.0.0/0" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.networkProfile.serviceCidr: Changing property 'properties.networkProfile.serviceCidr' is not allowed.",
+		},
+		{
+			name: "master subnetId change",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.MasterProfile.SubnetID = oc.Properties.MasterProfile.SubnetID[:strings.LastIndexByte(oc.Properties.MasterProfile.SubnetID, '/')] + "/changed"
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.masterProfile.subnetId: Changing property 'properties.masterProfile.subnetId' is not allowed.",
+		},
+		{
+			name:    "worker name change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.WorkerProfiles[0].Name = "new-name" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles['new-name'].name: Changing property 'properties.workerProfiles['new-name'].name' is not allowed.",
+		},
+		{
+			name:    "worker vmSize change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.WorkerProfiles[0].VMSize = "Standard_D8s_v3" },
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles['worker'].vmSize: Changing property 'properties.workerProfiles['worker'].vmSize' is not allowed.",
+		},
+		{
+			name:    "worker diskSizeGB change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.WorkerProfiles[0].DiskSizeGB++ },
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles['worker'].diskSizeGB: Changing property 'properties.workerProfiles['worker'].diskSizeGB' is not allowed.",
+		},
+		{
+			name: "worker subnetId change",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles[0].SubnetID = oc.Properties.WorkerProfiles[0].SubnetID[:strings.LastIndexByte(oc.Properties.WorkerProfiles[0].SubnetID, '/')] + "/changed"
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles['worker'].subnetId: Changing property 'properties.workerProfiles['worker'].subnetId' is not allowed.",
+		},
+		{
+			name:    "workerProfiles count change",
+			modify:  func(oc *OpenShiftCluster) { oc.Properties.WorkerProfiles[0].Count++ },
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles['worker'].count: Changing property 'properties.workerProfiles['worker'].count' is not allowed.",
+		},
+		{
+			name: "number of workerProfiles changes",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles = []WorkerProfile{{}, {}}
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles: Changing property 'properties.workerProfiles' is not allowed.",
+		},
+		{
+			name: "workerProfiles set to nil",
+			modify: func(oc *OpenShiftCluster) {
+				oc.Properties.WorkerProfiles = nil
+			},
+			wantErr: "400: PropertyChangeNotAllowed: properties.workerProfiles: Changing property 'properties.workerProfiles' is not allowed.",
+		},
+		{
+			name: "systemData set to empty",
+			modify: func(oc *OpenShiftCluster) {
+				oc.SystemData = &SystemData{}
+			},
+			wantErr: "400: PropertyChangeNotAllowed: systemData.createdBy: Changing property 'systemData.createdBy' is not allowed.",
+		},
+		{
+			name: "systemData LastUpdated changed",
+			modify: func(oc *OpenShiftCluster) {
+				oc.SystemData.LastModifiedBy = "Bob"
+			},
+			wantErr: "400: PropertyChangeNotAllowed: systemData.lastModifiedBy: Changing property 'systemData.lastModifiedBy' is not allowed.",
+		},
+	}
+
+	runTests(t, testModeUpdate, tests)
+}