diff --git a/api/v1alpha7/identity_types.go b/api/v1alpha7/identity_types.go index 15cd58562b..1ea5f2fb9b 100644 --- a/api/v1alpha7/identity_types.go +++ b/api/v1alpha7/identity_types.go @@ -16,8 +16,6 @@ limitations under the License. package v1alpha7 -const defaultIdentityRefKind = "Secret" - // OpenStackIdentityReference is a reference to an infrastructure // provider identity to be used to provision cluster resources. type OpenStackIdentityReference struct { diff --git a/api/v1alpha7/openstackcluster_webhook.go b/api/v1alpha7/openstackcluster_webhook.go deleted file mode 100644 index 706d10a8c6..0000000000 --- a/api/v1alpha7/openstackcluster_webhook.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "fmt" - "reflect" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/builder" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var _ = logf.Log.WithName("openstackcluster-resource") - -func (r *OpenStackCluster) SetupWebhookWithManager(mgr manager.Manager) error { - return builder.WebhookManagedBy(mgr). - For(r). - Complete() -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackcluster,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackclusters,versions=v1alpha7,name=validation.openstackcluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 -// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackcluster,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackclusters,versions=v1alpha7,name=default.openstackcluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 - -var ( - _ webhook.Defaulter = &OpenStackCluster{} - _ webhook.Validator = &OpenStackCluster{} -) - -// Default satisfies the defaulting webhook interface. -func (r *OpenStackCluster) Default() { - if r.Spec.IdentityRef != nil && r.Spec.IdentityRef.Kind == "" { - r.Spec.IdentityRef.Kind = defaultIdentityRefKind - } -} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackCluster) ValidateCreate() (admission.Warnings, error) { - var allErrs field.ErrorList - - if r.Spec.IdentityRef != nil && r.Spec.IdentityRef.Kind != defaultIdentityRefKind { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "identityRef", "kind"), "must be a Secret")) - } - - return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackCluster) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - old, ok := oldRaw.(*OpenStackCluster) - if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an OpenStackCluster but got a %T", oldRaw)) - } - - if r.Spec.IdentityRef != nil && r.Spec.IdentityRef.Kind != defaultIdentityRefKind { - allErrs = append(allErrs, - field.Invalid(field.NewPath("spec", "identityRef", "kind"), - r.Spec.IdentityRef, "must be a Secret"), - ) - } - - // Allow changes to Spec.IdentityRef.Name. - if old.Spec.IdentityRef != nil && r.Spec.IdentityRef != nil { - old.Spec.IdentityRef.Name = "" - r.Spec.IdentityRef.Name = "" - } - - // Allow changes to Spec.IdentityRef if it was unset. - if old.Spec.IdentityRef == nil && r.Spec.IdentityRef != nil { - old.Spec.IdentityRef = &OpenStackIdentityReference{} - r.Spec.IdentityRef = &OpenStackIdentityReference{} - } - - if old.Spec.IdentityRef != nil && r.Spec.IdentityRef == nil { - allErrs = append(allErrs, - field.Invalid(field.NewPath("spec", "identityRef"), - r.Spec.IdentityRef, "field cannot be set to nil"), - ) - } - - // Allow change only for the first time. - if old.Spec.ControlPlaneEndpoint.Host == "" { - old.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{} - r.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{} - } - - // Allow change only for the first time. - if old.Spec.DisableAPIServerFloatingIP && old.Spec.APIServerFixedIP == "" { - r.Spec.APIServerFixedIP = "" - } - - // If API Server floating IP is disabled, allow the change of the API Server port only for the first time. - if old.Spec.DisableAPIServerFloatingIP && old.Spec.APIServerPort == 0 && r.Spec.APIServerPort > 0 { - r.Spec.APIServerPort = 0 - } - - // Allow changes to the bastion spec. - old.Spec.Bastion = &Bastion{} - r.Spec.Bastion = &Bastion{} - - // Allow changes on AllowedCIDRs - if r.Spec.APIServerLoadBalancer.Enabled { - old.Spec.APIServerLoadBalancer.AllowedCIDRs = []string{} - r.Spec.APIServerLoadBalancer.AllowedCIDRs = []string{} - } - - // Allow changes to the availability zones. - old.Spec.ControlPlaneAvailabilityZones = []string{} - r.Spec.ControlPlaneAvailabilityZones = []string{} - - // Allow change to the allowAllInClusterTraffic. - old.Spec.AllowAllInClusterTraffic = false - r.Spec.AllowAllInClusterTraffic = false - - // Allow change on the spec.APIServerFloatingIP only if it matches the current api server loadbalancer IP. - if old.Status.APIServerLoadBalancer != nil && r.Spec.APIServerFloatingIP == old.Status.APIServerLoadBalancer.IP { - r.Spec.APIServerFloatingIP = "" - old.Spec.APIServerFloatingIP = "" - } - - if !reflect.DeepEqual(old.Spec, r.Spec) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "cannot be modified")) - } - - return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackCluster) ValidateDelete() (admission.Warnings, error) { - return nil, nil -} diff --git a/api/v1alpha7/openstackcluster_webhook_test.go b/api/v1alpha7/openstackcluster_webhook_test.go deleted file mode 100644 index 24e1407f2c..0000000000 --- a/api/v1alpha7/openstackcluster_webhook_test.go +++ /dev/null @@ -1,408 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "testing" - - . "github.com/onsi/gomega" -) - -func TestOpenStackCluster_ValidateUpdate(t *testing.T) { - g := NewWithT(t) - - tests := []struct { - name string - oldTemplate *OpenStackCluster - newTemplate *OpenStackCluster - wantErr bool - }{ - { - name: "OpenStackCluster.Spec.IdentityRef.Kind must always be Secret", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "Secret", - Name: "foobar", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "foobar", - Name: "foobar", - }, - }, - }, - wantErr: true, - }, - { - name: "Changing OpenStackCluster.Spec.IdentityRef.Name is allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "Secret", - Name: "foobar", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "Secret", - Name: "foobarbaz", - }, - }, - }, - wantErr: false, - }, - { - name: "OpenStackCluster.Spec.IdentityRef can be changed if it was unset", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "Secret", - Name: "foobar", - }, - }, - }, - wantErr: false, - }, - { - name: "OpenStackCluster.Spec.IdentityRef must not be removed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "Secret", - Name: "foobar", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - }, - }, - wantErr: true, - }, - { - name: "Changing OpenStackCluster.Spec.Bastion is allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - Bastion: &Bastion{ - Instance: OpenStackMachineSpec{ - CloudName: "foobar", - Image: "foobar", - Flavor: "minimal", - }, - Enabled: true, - }, - }, - Status: OpenStackClusterStatus{ - Bastion: &BastionStatus{ - Name: "foobar", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - Bastion: &Bastion{ - Instance: OpenStackMachineSpec{ - CloudName: "foobarbaz", - Image: "foobarbaz", - Flavor: "medium", - }, - Enabled: true, - }, - }, - }, - wantErr: false, - }, - { - name: "Changing CIDRs on the OpenStackCluster.Spec.APIServerLoadBalancer.AllowedCIDRs is allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - APIServerLoadBalancer: APIServerLoadBalancer{ - Enabled: true, - AllowedCIDRs: []string{ - "0.0.0.0/0", - "192.168.10.0/24", - }, - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - APIServerLoadBalancer: APIServerLoadBalancer{ - Enabled: true, - AllowedCIDRs: []string{ - "0.0.0.0/0", - "192.168.10.0/24", - "10.6.0.0/16", - }, - }, - }, - }, - wantErr: false, - }, - { - name: "Adding OpenStackCluster.Spec.ControlPlaneAvailabilityZones is allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - ControlPlaneAvailabilityZones: []string{ - "alice", - "bob", - }, - }, - }, - wantErr: false, - }, - { - name: "Modifying OpenStackCluster.Spec.ControlPlaneAvailabilityZones is allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - ControlPlaneAvailabilityZones: []string{ - "alice", - "bob", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - ControlPlaneAvailabilityZones: []string{ - "alice", - "bob", - "eve", - }, - }, - }, - wantErr: false, - }, - { - name: "Removing OpenStackCluster.Spec.ControlPlaneAvailabilityZones is allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - ControlPlaneAvailabilityZones: []string{ - "alice", - "bob", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - }, - }, - wantErr: false, - }, - { - name: "Changing OpenStackCluster.Spec.APIServerFixedIP is allowed when API Server Floating IP is disabled", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: true, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: true, - APIServerFixedIP: "20.1.56.1", - }, - }, - wantErr: false, - }, - { - name: "Changing OpenStackCluster.Spec.APIServerFixedIP is not allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: false, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: false, - APIServerFixedIP: "20.1.56.1", - }, - }, - wantErr: true, - }, - - { - name: "Changing OpenStackCluster.Spec.APIServerPort is allowed when API Server Floating IP is disabled", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: true, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: true, - APIServerPort: 8443, - }, - }, - wantErr: false, - }, - { - name: "Changing OpenStackCluster.Spec.APIServerPort is not allowed", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: false, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - DisableAPIServerFloatingIP: false, - APIServerPort: 8443, - }, - }, - wantErr: true, - }, - { - name: "Changing OpenStackCluster.Spec.APIServerFloatingIP is allowed when it matches the current api server loadbalancer IP", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - APIServerFloatingIP: "", - }, - Status: OpenStackClusterStatus{ - APIServerLoadBalancer: &LoadBalancer{ - IP: "1.2.3.4", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - APIServerFloatingIP: "1.2.3.4", - }, - Status: OpenStackClusterStatus{ - APIServerLoadBalancer: &LoadBalancer{ - IP: "1.2.3.4", - }, - }, - }, - wantErr: false, - }, - { - name: "Changing OpenStackCluster.Spec.APIServerFloatingIP is not allowed when it doesn't matches the current api server loadbalancer IP", - oldTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - APIServerFloatingIP: "", - }, - Status: OpenStackClusterStatus{ - APIServerLoadBalancer: &LoadBalancer{ - IP: "1.2.3.4", - }, - }, - }, - newTemplate: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - APIServerFloatingIP: "5.6.7.8", - }, - Status: OpenStackClusterStatus{ - APIServerLoadBalancer: &LoadBalancer{ - IP: "1.2.3.4", - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - warn, err := tt.newTemplate.ValidateUpdate(tt.oldTemplate) - if tt.wantErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } - // Nothing emits warnings yet - g.Expect(warn).To(BeEmpty()) - }) - } -} - -func TestOpenStackCluster_ValidateCreate(t *testing.T) { - g := NewWithT(t) - - tests := []struct { - name string - template *OpenStackCluster - wantErr bool - }{ - { - name: "OpenStackCluster.Spec.IdentityRef with correct spec on create", - template: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "Secret", - Name: "foobar", - }, - }, - }, - wantErr: false, - }, - { - name: "OpenStackCluster.Spec.IdentityRef with faulty spec on create", - template: &OpenStackCluster{ - Spec: OpenStackClusterSpec{ - CloudName: "foobar", - IdentityRef: &OpenStackIdentityReference{ - Kind: "foobar", - Name: "foobar", - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - warn, err := tt.template.ValidateCreate() - if tt.wantErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } - // Nothing emits warnings yet - g.Expect(warn).To(BeEmpty()) - }) - } -} diff --git a/api/v1alpha7/openstackclusterlist_webhook.go b/api/v1alpha7/openstackclusterlist_webhook.go deleted file mode 100644 index f7749b7ade..0000000000 --- a/api/v1alpha7/openstackclusterlist_webhook.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "sigs.k8s.io/controller-runtime/pkg/builder" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -// log is for logging in this package. -var _ = logf.Log.WithName("openstackclusterlist-resource") - -func (r *OpenStackClusterList) SetupWebhookWithManager(mgr manager.Manager) error { - return builder.WebhookManagedBy(mgr). - For(r). - Complete() -} diff --git a/api/v1alpha7/openstackclustertemplate_webhook.go b/api/v1alpha7/openstackclustertemplate_webhook.go deleted file mode 100644 index 337928a3f1..0000000000 --- a/api/v1alpha7/openstackclustertemplate_webhook.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "fmt" - "reflect" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -const openStackClusterTemplateImmutableMsg = "OpenStackClusterTemplate spec.template.spec field is immutable. Please create new resource instead." - -func (r *OpenStackClusterTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackclustertemplate,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackclustertemplates,versions=v1alpha7,name=default.openstackclustertemplate.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 -// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackclustertemplate,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackclustertemplates,versions=v1alpha7,name=validation.openstackclustertemplate.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 - -var ( - _ webhook.Defaulter = &OpenStackClusterTemplate{} - _ webhook.Validator = &OpenStackClusterTemplate{} -) - -// Default implements webhook.Defaulter so a webhook will be registered for the type. -func (r *OpenStackClusterTemplate) Default() { - if r.Spec.Template.Spec.IdentityRef != nil && r.Spec.Template.Spec.IdentityRef.Kind == "" { - r.Spec.Template.Spec.IdentityRef.Kind = defaultIdentityRefKind - } -} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackClusterTemplate) ValidateCreate() (admission.Warnings, error) { - var allErrs field.ErrorList - - if r.Spec.Template.Spec.IdentityRef != nil && r.Spec.Template.Spec.IdentityRef.Kind != defaultIdentityRefKind { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "template", "spec", "identityRef", "kind"), "must be a Secret")) - } - - return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackClusterTemplate) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - old, ok := oldRaw.(*OpenStackClusterTemplate) - if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an OpenStackClusterTemplate but got a %T", oldRaw)) - } - - if !reflect.DeepEqual(r.Spec.Template.Spec, old.Spec.Template.Spec) { - allErrs = append(allErrs, - field.Invalid(field.NewPath("OpenStackClusterTemplate", "spec", "template", "spec"), r, openStackClusterTemplateImmutableMsg), - ) - } - - return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackClusterTemplate) ValidateDelete() (admission.Warnings, error) { - return nil, nil -} diff --git a/api/v1alpha7/openstackmachine_webhook.go b/api/v1alpha7/openstackmachine_webhook.go deleted file mode 100644 index 6470589a76..0000000000 --- a/api/v1alpha7/openstackmachine_webhook.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "fmt" - "reflect" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/controller-runtime/pkg/builder" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var _ = logf.Log.WithName("openstackmachine-resource") - -func (r *OpenStackMachine) SetupWebhookWithManager(mgr manager.Manager) error { - return builder.WebhookManagedBy(mgr). - For(r). - Complete() -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackmachine,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackmachines,versions=v1alpha7,name=validation.openstackmachine.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 -// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackmachine,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackmachines,versions=v1alpha7,name=default.openstackmachine.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 - -var ( - _ webhook.Defaulter = &OpenStackMachine{} - _ webhook.Validator = &OpenStackMachine{} -) - -// Default satisfies the defaulting webhook interface. -func (r *OpenStackMachine) Default() { - if r.Spec.IdentityRef != nil && r.Spec.IdentityRef.Kind == "" { - r.Spec.IdentityRef.Kind = defaultIdentityRefKind - } -} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackMachine) ValidateCreate() (admission.Warnings, error) { - var allErrs field.ErrorList - - if r.Spec.IdentityRef != nil && r.Spec.IdentityRef.Kind != defaultIdentityRefKind { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "identityRef", "kind"), "must be a Secret")) - } - - if r.Spec.RootVolume != nil && r.Spec.AdditionalBlockDevices != nil { - for _, device := range r.Spec.AdditionalBlockDevices { - if device.Name == "root" { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "additionalBlockDevices"), "cannot contain a device named \"root\" when rootVolume is set")) - } - } - } - - return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackMachine) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - newOpenStackMachine, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r) - if err != nil { - return nil, apierrors.NewInvalid(GroupVersion.WithKind("OpenStackMachine").GroupKind(), r.Name, field.ErrorList{ - field.InternalError(nil, fmt.Errorf("failed to convert new OpenStackMachine to unstructured object: %w", err)), - }) - } - oldOpenStackMachine, err := runtime.DefaultUnstructuredConverter.ToUnstructured(old) - if err != nil { - return nil, apierrors.NewInvalid(GroupVersion.WithKind("OpenStackMachine").GroupKind(), r.Name, field.ErrorList{ - field.InternalError(nil, fmt.Errorf("failed to convert old OpenStackMachine to unstructured object: %w", err)), - }) - } - - var allErrs field.ErrorList - - if r.Spec.IdentityRef != nil && r.Spec.IdentityRef.Kind != defaultIdentityRefKind { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "identityRef", "kind"), "must be a Secret")) - } - - newOpenStackMachineSpec := newOpenStackMachine["spec"].(map[string]interface{}) - oldOpenStackMachineSpec := oldOpenStackMachine["spec"].(map[string]interface{}) - - // allow changes to providerID once - if oldOpenStackMachineSpec["providerID"] == nil { - delete(oldOpenStackMachineSpec, "providerID") - delete(newOpenStackMachineSpec, "providerID") - } - - // allow changes to instanceID once - if oldOpenStackMachineSpec["instanceID"] == nil { - delete(oldOpenStackMachineSpec, "instanceID") - delete(newOpenStackMachineSpec, "instanceID") - } - - if !reflect.DeepEqual(oldOpenStackMachineSpec, newOpenStackMachineSpec) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "cannot be modified")) - } - - return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. -func (r *OpenStackMachine) ValidateDelete() (admission.Warnings, error) { - return nil, nil -} diff --git a/api/v1alpha7/openstackmachinelist_webhook.go b/api/v1alpha7/openstackmachinelist_webhook.go deleted file mode 100644 index 0122e1b52c..0000000000 --- a/api/v1alpha7/openstackmachinelist_webhook.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "sigs.k8s.io/controller-runtime/pkg/builder" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -// log is for logging in this package. -var _ = logf.Log.WithName("openstackmachinelist-resource") - -func (r *OpenStackMachineList) SetupWebhookWithManager(mgr manager.Manager) error { - return builder.WebhookManagedBy(mgr). - For(r). - Complete() -} diff --git a/api/v1alpha7/openstackmachinetemplate_webhook.go b/api/v1alpha7/openstackmachinetemplate_webhook.go deleted file mode 100644 index b74d056ba2..0000000000 --- a/api/v1alpha7/openstackmachinetemplate_webhook.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "context" - "fmt" - "reflect" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/cluster-api/util/topology" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// OpenStackMachineTemplateImmutableMsg ... -const OpenStackMachineTemplateImmutableMsg = "OpenStackMachineTemplate spec.template.spec field is immutable. Please create a new resource instead. Ref doc: https://cluster-api.sigs.k8s.io/tasks/change-machine-template.html" - -// +kubebuilder:object:generate=false -type OpenStackMachineTemplateWebhook struct{} - -func (r *OpenStackMachineTemplateWebhook) SetupWebhookWithManager(mgr manager.Manager) error { - return builder.WebhookManagedBy(mgr). - For(&OpenStackMachineTemplate{}). - WithValidator(r). - Complete() -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1alpha7-openstackmachinetemplate,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=openstackmachinetemplates,versions=v1alpha7,name=validation.openstackmachinetemplate.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1 - -var _ webhook.CustomValidator = &OpenStackMachineTemplateWebhook{} - -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type. -func (r *OpenStackMachineTemplateWebhook) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { - openStackMachineTemplate, ok := obj.(*OpenStackMachineTemplate) - if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an OpenStackMachineTemplate but got a %T", obj)) - } - - var allErrs field.ErrorList - - if openStackMachineTemplate.Spec.Template.Spec.ProviderID != nil { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "template", "spec", "providerID"), "cannot be set in templates")) - } - - return aggregateObjErrors(openStackMachineTemplate.GroupVersionKind().GroupKind(), openStackMachineTemplate.Name, allErrs) -} - -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type. -func (r *OpenStackMachineTemplateWebhook) ValidateUpdate(ctx context.Context, oldRaw runtime.Object, newRaw runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - old, ok := oldRaw.(*OpenStackMachineTemplate) - if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an OpenStackMachineTemplate but got a %T", oldRaw)) - } - - newObj, ok := newRaw.(*OpenStackMachineTemplate) - if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an OpenStackMachineTemplate but got a %T", oldRaw)) - } - - req, err := admission.RequestFromContext(ctx) - if err != nil { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a admission.Request inside context: %v", err)) - } - - if !topology.ShouldSkipImmutabilityChecks(req, newObj) && - !reflect.DeepEqual(newObj.Spec.Template.Spec, old.Spec.Template.Spec) { - allErrs = append(allErrs, - field.Invalid(field.NewPath("spec", "template", "spec"), r, OpenStackMachineTemplateImmutableMsg), - ) - } - - return aggregateObjErrors(newObj.GroupVersionKind().GroupKind(), newObj.Name, allErrs) -} - -// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type. -func (r *OpenStackMachineTemplateWebhook) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) { - return nil, nil -} diff --git a/api/v1alpha7/openstackmachinetemplate_webhook_test.go b/api/v1alpha7/openstackmachinetemplate_webhook_test.go deleted file mode 100644 index d9a53adc17..0000000000 --- a/api/v1alpha7/openstackmachinetemplate_webhook_test.go +++ /dev/null @@ -1,170 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "context" - "testing" - - . "github.com/onsi/gomega" - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -func TestOpenStackMachineTemplate_ValidateUpdate(t *testing.T) { - g := NewWithT(t) - - tests := []struct { - name string - oldTemplate *OpenStackMachineTemplate - newTemplate *OpenStackMachineTemplate - req *admission.Request - wantErr bool - }{ - { - name: "OpenStackMachineTemplate with immutable spec", - oldTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "bar", - }, - }, - }, - }, - newTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "NewImage", - }, - }, - }, - }, - req: &admission.Request{}, - wantErr: true, - }, - { - name: "OpenStackMachineTemplate with mutable metadata", - oldTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "bar", - }, - }, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, - newTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "bar", - }, - }, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - }, - }, - req: &admission.Request{}, - }, - { - name: "don't allow modification, dry run, no skip immutability annotation set", - oldTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "bar", - }, - }, - }, - }, - newTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "NewImage", - }, - }, - }, - }, - req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: pointer.Bool(true)}}, - wantErr: true, - }, - { - name: "allow modification, dry run, skip immutability annotation set", - oldTemplate: &OpenStackMachineTemplate{ - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "bar", - }, - }, - }, - }, - newTemplate: &OpenStackMachineTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - clusterv1.TopologyDryRunAnnotation: "", - }, - }, - Spec: OpenStackMachineTemplateSpec{ - Template: OpenStackMachineTemplateResource{ - Spec: OpenStackMachineSpec{ - Flavor: "foo", - Image: "NewImage", - }, - }, - }, - }, - req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: pointer.Bool(true)}}, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - webhook := &OpenStackMachineTemplateWebhook{} - ctx := admission.NewContextWithRequest(context.Background(), *tt.req) - - warn, err := webhook.ValidateUpdate(ctx, tt.oldTemplate, tt.newTemplate) - if tt.wantErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } - // Nothing emits warnings yet - g.Expect(warn).To(BeEmpty()) - }) - } -} diff --git a/api/v1alpha7/openstackmachinetemplatelist_webhook.go b/api/v1alpha7/openstackmachinetemplatelist_webhook.go deleted file mode 100644 index c1ff00c3dd..0000000000 --- a/api/v1alpha7/openstackmachinetemplatelist_webhook.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - "sigs.k8s.io/controller-runtime/pkg/builder" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -// log is for logging in this package. -var _ = logf.Log.WithName("openstackmachinetemplatelist-resource") - -func (r *OpenStackMachineTemplateList) SetupWebhookWithManager(mgr manager.Manager) error { - return builder.WebhookManagedBy(mgr). - For(r). - Complete() -} diff --git a/api/v1alpha7/webhooks.go b/api/v1alpha7/webhooks.go deleted file mode 100644 index d58fdbb155..0000000000 --- a/api/v1alpha7/webhooks.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha7 - -import ( - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -func aggregateObjErrors(gk schema.GroupKind, name string, allErrs field.ErrorList) (admission.Warnings, error) { - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - gk, - name, - allErrs, - ) -} diff --git a/main.go b/main.go index 771fc4c323..7410bccb47 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,6 @@ import ( client "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/webhook" infrav1alpha5 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5" infrav1alpha6 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha6" @@ -70,8 +69,6 @@ var ( syncPeriod time.Duration restConfigQPS float32 restConfigBurst int - webhookPort int - webhookCertDir string healthAddr string lbProvider string caCertsPath string @@ -134,12 +131,6 @@ func InitFlags(fs *pflag.FlagSet) { fs.IntVar(&restConfigBurst, "kube-api-burst", 30, "Maximum number of queries that should be allowed in one burst from the controller client to the Kubernetes API server. Defaults to 30") - fs.IntVar(&webhookPort, "webhook-port", 9443, - "Webhook Server port") - - fs.StringVar(&webhookCertDir, "webhook-cert-dir", "/tmp/k8s-webhook-server/serving-certs/", - "Webhook cert dir, only used when webhook-port is specified.") - fs.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.") @@ -228,12 +219,6 @@ func main() { }, }, }, - WebhookServer: webhook.NewServer( - webhook.Options{ - Port: webhookPort, - CertDir: webhookCertDir, - }, - ), HealthProbeBindAddress: healthAddr, }) if err != nil { @@ -249,9 +234,7 @@ func main() { scopeFactory := scope.NewFactory(scopeCacheMaxSize) - setupChecks(mgr) setupReconcilers(ctx, mgr, caCerts, scopeFactory) - setupWebhooks(mgr) // +kubebuilder:scaffold:builder setupLog.Info("starting manager", "version", version.Get().String()) @@ -261,18 +244,6 @@ func main() { } } -func setupChecks(mgr ctrl.Manager) { - if err := mgr.AddReadyzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil { - setupLog.Error(err, "unable to create ready check") - os.Exit(1) - } - - if err := mgr.AddHealthzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil { - setupLog.Error(err, "unable to create health check") - os.Exit(1) - } -} - func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte, scopeFactory scope.Factory) { if err := (&controllers.OpenStackClusterReconciler{ Client: mgr.GetClient(), @@ -296,37 +267,6 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte, sco } } -func setupWebhooks(mgr ctrl.Manager) { - if err := (&infrav1.OpenStackMachineTemplateWebhook{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackMachineTemplate") - os.Exit(1) - } - if err := (&infrav1.OpenStackMachineTemplateList{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackMachineTemplateList") - os.Exit(1) - } - if err := (&infrav1.OpenStackCluster{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackCluster") - os.Exit(1) - } - if err := (&infrav1.OpenStackClusterTemplate{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackClusterTemplate") - os.Exit(1) - } - if err := (&infrav1.OpenStackMachine{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackMachine") - os.Exit(1) - } - if err := (&infrav1.OpenStackMachineList{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackMachineList") - os.Exit(1) - } - if err := (&infrav1.OpenStackClusterList{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackClusterList") - os.Exit(1) - } -} - func concurrency(c int) controller.Options { return controller.Options{MaxConcurrentReconciles: c} }