Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions azure/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ const (
ProviderIDPrefix = "azure://"
)

const (
// CustomHeaderPrefix is the prefix of annotations that enable additional cluster / node pool features.
// Whatever follows the prefix will be passed as a header to cluster/node pool creation/update requests.
// E.g. add `"infrastructure.cluster.x-k8s.io/custom-header-UseGPUDedicatedVHD": "true"` annotation to
// AzureManagedMachinePool CR to enable creating GPU nodes by the node pool.
CustomHeaderPrefix = "infrastructure.cluster.x-k8s.io/custom-header-"
)

var (
// LinuxBootstrapExtensionCommand is the command the VM bootstrap extension will execute to verify Linux nodes bootstrap completes successfully.
LinuxBootstrapExtensionCommand = fmt.Sprintf("for i in $(seq 1 %d); do test -f %s && break; if [ $i -eq %d ]; then return 1; else sleep %d; fi; done", bootstrapExtensionRetries, bootstrapSentinelFile, bootstrapExtensionRetries, bootstrapExtensionSleep)
Expand Down
8 changes: 8 additions & 0 deletions azure/scope/managedcontrolplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,10 @@ func (s *ManagedControlPlaneScope) FailureDomains() []string {
return []string{}
}

func (s *ManagedControlPlaneScope) ManagedClusterAnnotations() map[string]string {
return s.ControlPlane.Annotations
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:
Do we need this function? Can't we simply use s.ControlPlane.Annotations at places wherever required?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see why it is so. You basically implemented it as part of ManagedMachinePoolScope interface.

Copy link
Copy Markdown
Contributor Author

@michalno1 michalno1 Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular method implements ManagedClusterScope rather than ManagedMachinePoolScope. This is done in the same spirit as ManagedControlPlaneScope.ManagedClusterSpec().

// ManagedClusterSpec returns the managed cluster spec.
func (s *ManagedControlPlaneScope) ManagedClusterSpec() (azure.ManagedClusterSpec, error) {
decodedSSHPublicKey, err := base64.StdEncoding.DecodeString(s.ControlPlane.Spec.SSHPublicKey)
Expand Down Expand Up @@ -557,6 +561,10 @@ func (s *ManagedControlPlaneScope) GetAllAgentPoolSpecs(ctx context.Context) ([]
return ammps, nil
}

func (s *ManagedControlPlaneScope) AgentPoolAnnotations() map[string]string {
return s.InfraMachinePool.Annotations
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

// AgentPoolSpec returns an azure.AgentPoolSpec for currently reconciled AzureManagedMachinePool.
func (s *ManagedControlPlaneScope) AgentPoolSpec() azure.AgentPoolSpec {
return buildAgentPoolSpec(s.ControlPlane, s.MachinePool, s.InfraMachinePool)
Expand Down
10 changes: 7 additions & 3 deletions azure/services/agentpools/agentpools.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/pkg/errors"
infrav1alpha4 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/util/maps"
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
)

Expand All @@ -35,6 +36,7 @@ type ManagedMachinePoolScope interface {
azure.ClusterDescriber

NodeResourceGroup() string
AgentPoolAnnotations() map[string]string
AgentPoolSpec() azure.AgentPoolSpec
SetAgentPoolProviderIDList([]string)
SetAgentPoolReplicas(int32)
Expand Down Expand Up @@ -64,7 +66,6 @@ func (s *Service) Reconcile(ctx context.Context) error {
defer done()

agentPoolSpec := s.scope.AgentPoolSpec()

profile := containerservice.AgentPool{
ManagedClusterAgentPoolProfileProperties: &containerservice.ManagedClusterAgentPoolProfileProperties{
VMSize: &agentPoolSpec.SKU,
Expand Down Expand Up @@ -96,8 +97,10 @@ func (s *Service) Reconcile(ctx context.Context) error {
// AKS will populate defaults and read-only values, which we want
// to strip/clean to match what we expect.

customHeaders := maps.FilterByKeyPrefix(s.scope.AgentPoolAnnotations(), azure.CustomHeaderPrefix)
if isCreate := azure.ResourceNotFound(err); isCreate {
err = s.Client.CreateOrUpdate(ctx, agentPoolSpec.ResourceGroup, agentPoolSpec.Cluster, agentPoolSpec.Name, profile)
err = s.Client.CreateOrUpdate(ctx, agentPoolSpec.ResourceGroup, agentPoolSpec.Cluster, agentPoolSpec.Name,
profile, customHeaders)
if err != nil && azure.ResourceNotFound(err) {
return azure.WithTransientError(errors.Wrap(err, "agent pool dependent resource does not exist yet"), 20*time.Second)
} else if err != nil {
Expand Down Expand Up @@ -138,7 +141,8 @@ func (s *Service) Reconcile(ctx context.Context) error {
diff := cmp.Diff(normalizedProfile, existingProfile)
if diff != "" {
log.V(2).Info(fmt.Sprintf("Update required (+new -old):\n%s", diff))
err = s.Client.CreateOrUpdate(ctx, agentPoolSpec.ResourceGroup, agentPoolSpec.Cluster, agentPoolSpec.Name, profile)
err = s.Client.CreateOrUpdate(ctx, agentPoolSpec.ResourceGroup, agentPoolSpec.Cluster, agentPoolSpec.Name,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we enable/disable features after node pool creation?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it depends on the feature, the headers are usually used with experimental features in AKS so lets say in case of GPU nodepool, we cannot. We cannot convert an existing agentpool to support GPU, its just a creation operation.

I'm ok with providing a way for users to use these experimental features and at most add some warning documentation about untested behaviors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the most sane way to move forward is to support "enable feature via custom header" on create only. There may be some aks nodepool update operations that are initiated via an "empty" PUT w/ HTTP header data, but that's kind of weird from a UX perspective and I would not expect to rely upon that.

As @zmalik suggests, we should consider feature configuration delivered via HTTP headers as an interface for AKS to rapidly deliver experimental features, and then eventually they may graduate to fully supported features (after which point capz would have to implement them in their final form).

Does that make sense? Is the PR as-is implemented to forbid updates of custom headers after cluster/nodepool creation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR right now allows for updates, although not intentionally. If there is a non zero diff along with annotations changes, the new headers will be set. I'd rather we be more explicit by adding a validation webhook to make it immutable. wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shysank added immutability check

profile, customHeaders)
if err != nil {
return errors.Wrap(err, "failed to create or update agent pool")
}
Expand Down
10 changes: 5 additions & 5 deletions azure/services/agentpools/agentpools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestReconcile(t *testing.T) {
expectedError: "",
expect: func(m *mock_agentpools.MockClientMockRecorder, provisioningstate string) {
pv := provisioningstate
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agentpool", gomock.Any()).Return(nil)
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agentpool", gomock.Any(), gomock.Any()).Return(nil)
m.Get(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agentpool").Return(containerservice.AgentPool{ManagedClusterAgentPoolProfileProperties: &containerservice.ManagedClusterAgentPoolProfileProperties{
ProvisioningState: &pv,
}}, nil)
Expand Down Expand Up @@ -144,7 +144,7 @@ func TestReconcile(t *testing.T) {
},
expectedError: "",
expect: func(m *mock_agentpools.MockClientMockRecorder) {
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agentpool", gomock.Any()).Return(nil)
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agentpool", gomock.Any(), gomock.Any()).Return(nil)
m.Get(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agentpool").Return(containerservice.AgentPool{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not Found"))
},
},
Expand Down Expand Up @@ -182,7 +182,7 @@ func TestReconcile(t *testing.T) {
expectedError: "",
expect: func(m *mock_agentpools.MockClientMockRecorder) {
m.Get(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool").Return(containerservice.AgentPool{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found"))
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool", gomock.AssignableToTypeOf(containerservice.AgentPool{})).Return(nil)
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool", gomock.AssignableToTypeOf(containerservice.AgentPool{}), gomock.Any()).Return(nil)
},
},
{
Expand All @@ -201,7 +201,7 @@ func TestReconcile(t *testing.T) {
expectedError: "failed to create or update agent pool: #: Internal Server Error: StatusCode=500",
expect: func(m *mock_agentpools.MockClientMockRecorder) {
m.Get(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool").Return(containerservice.AgentPool{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found"))
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool", gomock.AssignableToTypeOf(containerservice.AgentPool{})).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error"))
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool", gomock.AssignableToTypeOf(containerservice.AgentPool{}), gomock.Any()).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error"))
},
},
{
Expand All @@ -228,7 +228,7 @@ func TestReconcile(t *testing.T) {
ProvisioningState: to.StringPtr("Failed"),
},
}, nil)
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool", gomock.AssignableToTypeOf(containerservice.AgentPool{})).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error"))
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-cluster", "my-agent-pool", gomock.AssignableToTypeOf(containerservice.AgentPool{}), gomock.Any()).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error"))
},
},
{
Expand Down
15 changes: 12 additions & 3 deletions azure/services/agentpools/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
// Client wraps go-sdk.
type Client interface {
Get(context.Context, string, string, string) (containerservice.AgentPool, error)
CreateOrUpdate(context.Context, string, string, string, containerservice.AgentPool) error
CreateOrUpdate(context.Context, string, string, string, containerservice.AgentPool, map[string]string) error
Delete(context.Context, string, string, string) error
}

Expand Down Expand Up @@ -62,11 +62,20 @@ func (ac *AzureClient) Get(ctx context.Context, resourceGroupName, cluster, name
}

// CreateOrUpdate creates or updates an agent pool.
func (ac *AzureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, cluster, name string, properties containerservice.AgentPool) error {
func (ac *AzureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, cluster, name string,
properties containerservice.AgentPool, customHeaders map[string]string) error {
ctx, _, done := tele.StartSpanWithLogger(ctx, "agentpools.AzureClient.CreateOrUpdate")
defer done()

future, err := ac.agentpools.CreateOrUpdate(ctx, resourceGroupName, cluster, name, properties)
preparer, err := ac.agentpools.CreateOrUpdatePreparer(ctx, resourceGroupName, cluster, name, properties)
if err != nil {
return errors.Wrap(err, "failed to prepare operation")
}
for key, element := range customHeaders {
preparer.Header.Add(key, element)
}

future, err := ac.agentpools.CreateOrUpdateSender(preparer)
if err != nil {
return errors.Wrap(err, "failed to begin operation")
}
Expand Down
8 changes: 4 additions & 4 deletions azure/services/agentpools/mock_agentpools/agentpools_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions azure/services/managedclusters/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
type Client interface {
Get(context.Context, string, string) (containerservice.ManagedCluster, error)
GetCredentials(context.Context, string, string) ([]byte, error)
CreateOrUpdate(context.Context, string, string, containerservice.ManagedCluster) (containerservice.ManagedCluster, error)
CreateOrUpdate(context.Context, string, string, containerservice.ManagedCluster, map[string]string) (containerservice.ManagedCluster, error)
Delete(context.Context, string, string) error
}

Expand Down Expand Up @@ -78,11 +78,19 @@ func (ac *AzureClient) GetCredentials(ctx context.Context, resourceGroupName, na
}

// CreateOrUpdate creates or updates a managed cluster.
func (ac *AzureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, name string, cluster containerservice.ManagedCluster) (containerservice.ManagedCluster, error) {
func (ac *AzureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, name string, cluster containerservice.ManagedCluster, headers map[string]string) (containerservice.ManagedCluster, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is another case where it won't work super well with the async interface once we move this func to async... @shysank @Jont828 @sonasingh46

For now I think it's fine to add it in here as-is but we'll need to have a way to expand ResourceSpecGetter when custom fields are needed like here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually for this one particularly @jackfrancis pointed out it would be something useful to have for all clients, so we could just add it as a generic capability of ResourceSpecGetter (ie. spec.CustomHeaders). Not a comment for this PR, just thinking out loud.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I think it's fine to add it in here as-is but we'll need to have a way to expand ResourceSpecGetter when custom fields are needed like here

Right. Also, I had some ideas on this one that I pasted on slack sometime back. I will open up an issue/discussion -- highlighting the challenges that we faced and we can actually brainstorm/groom to get to some solution.

ctx, _, done := tele.StartSpanWithLogger(ctx, "managedclusters.AzureClient.CreateOrUpdate")
defer done()

future, err := ac.managedclusters.CreateOrUpdate(ctx, resourceGroupName, name, cluster)
preparer, err := ac.managedclusters.CreateOrUpdatePreparer(ctx, resourceGroupName, name, cluster)
if err != nil {
return containerservice.ManagedCluster{}, errors.Wrap(err, "failed to prepare operation")
}
for key, value := range headers {
preparer.Header.Add(key, value)
}

future, err := ac.managedclusters.CreateOrUpdateSender(preparer)
if err != nil {
return containerservice.ManagedCluster{}, errors.Wrap(err, "failed to begin operation")
}
Expand Down
7 changes: 5 additions & 2 deletions azure/services/managedclusters/managedclusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/klog/v2"
infrav1alpha4 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/util/maps"
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)
Expand All @@ -42,6 +43,7 @@ var (
// ManagedClusterScope defines the scope interface for a managed cluster.
type ManagedClusterScope interface {
azure.ClusterDescriber
ManagedClusterAnnotations() map[string]string
ManagedClusterSpec() (azure.ManagedClusterSpec, error)
GetAllAgentPoolSpecs(ctx context.Context) ([]azure.AgentPoolSpec, error)
SetControlPlaneEndpoint(clusterv1.APIEndpoint)
Expand Down Expand Up @@ -292,8 +294,9 @@ func (s *Service) Reconcile(ctx context.Context) error {
}
}

customHeaders := maps.FilterByKeyPrefix(s.Scope.ManagedClusterAnnotations(), azure.CustomHeaderPrefix)
if isCreate {
managedCluster, err = s.Client.CreateOrUpdate(ctx, managedClusterSpec.ResourceGroupName, managedClusterSpec.Name, managedCluster)
managedCluster, err = s.Client.CreateOrUpdate(ctx, managedClusterSpec.ResourceGroupName, managedClusterSpec.Name, managedCluster, customHeaders)
if err != nil {
return fmt.Errorf("failed to create managed cluster, %w", err)
}
Expand Down Expand Up @@ -322,7 +325,7 @@ func (s *Service) Reconcile(ctx context.Context) error {
diff := computeDiffOfNormalizedClusters(managedCluster, existingMC)
if diff != "" {
klog.V(2).Infof("Update required (+new -old):\n%s", diff)
managedCluster, err = s.Client.CreateOrUpdate(ctx, managedClusterSpec.ResourceGroupName, managedClusterSpec.Name, managedCluster)
managedCluster, err = s.Client.CreateOrUpdate(ctx, managedClusterSpec.ResourceGroupName, managedClusterSpec.Name, managedCluster, customHeaders)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar comment as above regarding enabling/disabling features after cluster creation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shysank added validation webhook with immutability check to AzureManagedCluster as well.

if err != nil {
return fmt.Errorf("failed to update managed cluster, %w", err)
}
Expand Down
9 changes: 7 additions & 2 deletions azure/services/managedclusters/managedclusters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestReconcile(t *testing.T) {
provisioningStatesToTest: []string{"Canceled", "Succeeded", "Failed"},
expectedError: "",
expect: func(m *mock_managedclusters.MockClientMockRecorder, provisioningstate string, s *mock_managedclusters.MockManagedClusterScopeMockRecorder) {
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-managedcluster", gomock.Any()).Return(containerservice.ManagedCluster{ManagedClusterProperties: &containerservice.ManagedClusterProperties{
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-managedcluster", gomock.Any(), map[string]string{"myFeature": "true"}).Return(containerservice.ManagedCluster{ManagedClusterProperties: &containerservice.ManagedClusterProperties{
Fqdn: pointer.String("my-managedcluster-fqdn"),
ProvisioningState: &provisioningstate,
}}, nil)
Expand All @@ -55,6 +55,9 @@ func TestReconcile(t *testing.T) {
m.GetCredentials(gomockinternal.AContext(), "my-rg", "my-managedcluster").Times(1)
s.ClusterName().AnyTimes().Return("my-managedcluster")
s.ResourceGroup().AnyTimes().Return("my-rg")
s.ManagedClusterAnnotations().Times(1).Return(map[string]string{
"infrastructure.cluster.x-k8s.io/custom-header-myFeature": "true",
})
s.ManagedClusterSpec().AnyTimes().Return(azure.ManagedClusterSpec{
Name: "my-managedcluster",
ResourceGroupName: "my-rg",
Expand All @@ -73,6 +76,7 @@ func TestReconcile(t *testing.T) {
}}, nil)
s.ClusterName().AnyTimes().Return("my-managedcluster")
s.ResourceGroup().AnyTimes().Return("my-rg")
s.ManagedClusterAnnotations().Times(1).Return(map[string]string{})
s.ManagedClusterSpec().AnyTimes().Return(azure.ManagedClusterSpec{
Name: "my-managedcluster",
ResourceGroupName: "my-rg",
Expand Down Expand Up @@ -120,11 +124,12 @@ func TestReconcile(t *testing.T) {
name: "no managedcluster exists",
expectedError: "",
expect: func(m *mock_managedclusters.MockClientMockRecorder, s *mock_managedclusters.MockManagedClusterScopeMockRecorder) {
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-managedcluster", gomock.Any()).Return(containerservice.ManagedCluster{ManagedClusterProperties: &containerservice.ManagedClusterProperties{}}, nil)
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-managedcluster", gomock.Any(), gomock.Any()).Return(containerservice.ManagedCluster{ManagedClusterProperties: &containerservice.ManagedClusterProperties{}}, nil)
m.Get(gomockinternal.AContext(), "my-rg", "my-managedcluster").Return(containerservice.ManagedCluster{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not Found"))
m.GetCredentials(gomockinternal.AContext(), "my-rg", "my-managedcluster").Times(1)
s.ClusterName().AnyTimes().Return("my-managedcluster")
s.ResourceGroup().AnyTimes().Return("my-rg")
s.ManagedClusterAnnotations().Times(1).Return(map[string]string{})
s.ManagedClusterSpec().AnyTimes().Return(azure.ManagedClusterSpec{
Name: "my-managedcluster",
ResourceGroupName: "my-rg",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,26 @@ webhooks:
resources:
- azuremachinepoolmachines
sideEffects: None
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremanagedcluster
failurePolicy: Fail
name: validation.azuremanagedclusters.infrastructure.cluster.x-k8s.io
rules:
- apiGroups:
- infrastructure.cluster.x-k8s.io
apiVersions:
- v1beta1
operations:
- UPDATE
resources:
- azuremanagedclusters
sideEffects: None
- admissionReviewVersions:
- v1
- v1beta1
Expand Down
Loading