Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
ARG ARCH

# Build the manager binary
FROM golang:1.22 AS builder
FROM golang:1.23 AS builder
WORKDIR /workspace

# Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy
Expand Down
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ ifneq ($(abspath $(ROOT_DIR)),$(GOPATH)/src/sigs.k8s.io/cluster-api-provider-azu
endif

# Binaries.
CONTROLLER_GEN_VER := v0.16.1
CONTROLLER_GEN_VER := v0.17.2
CONTROLLER_GEN_BIN := controller-gen
CONTROLLER_GEN := $(TOOLS_BIN_DIR)/$(CONTROLLER_GEN_BIN)-$(CONTROLLER_GEN_VER)

CONVERSION_GEN_VER := v0.31.0
CONVERSION_GEN_VER := v0.32.2
CONVERSION_GEN_BIN := conversion-gen
CONVERSION_GEN := $(TOOLS_BIN_DIR)/$(CONVERSION_GEN_BIN)-$(CONVERSION_GEN_VER)

Expand All @@ -86,7 +86,7 @@ GOLANGCI_LINT_VER := $(shell cat .github/workflows/pr-golangci-lint.yaml | grep
GOLANGCI_LINT_BIN := golangci-lint
GOLANGCI_LINT := $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN)-$(GOLANGCI_LINT_VER)

KUSTOMIZE_VER := v5.4.1
KUSTOMIZE_VER := v5.6.0
KUSTOMIZE_BIN := kustomize
KUSTOMIZE := $(TOOLS_BIN_DIR)/$(KUSTOMIZE_BIN)-$(KUSTOMIZE_VER)

Expand Down Expand Up @@ -135,7 +135,7 @@ CODESPELL_BIN := codespell
CODESPELL_DIST_DIR := codespell_dist
CODESPELL := $(TOOLS_BIN_DIR)/$(CODESPELL_DIST_DIR)/$(CODESPELL_BIN)

SETUP_ENVTEST_VER := release-0.19
SETUP_ENVTEST_VER := release-0.20
SETUP_ENVTEST_BIN := setup-envtest
SETUP_ENVTEST := $(abspath $(TOOLS_BIN_DIR)/$(SETUP_ENVTEST_BIN)-$(SETUP_ENVTEST_VER))
SETUP_ENVTEST_PKG := sigs.k8s.io/controller-runtime/tools/setup-envtest
Expand Down Expand Up @@ -168,7 +168,7 @@ CRD_ROOT ?= $(MANIFEST_ROOT)/crd/bases
WEBHOOK_ROOT ?= $(MANIFEST_ROOT)/webhook
RBAC_ROOT ?= $(MANIFEST_ROOT)/rbac
ASO_CRDS_PATH := $(MANIFEST_ROOT)/aso/crds.yaml
ASO_VERSION := v2.11.0
ASO_VERSION := v2.12.0
ASO_CRDS := resourcegroups.resources.azure.com natgateways.network.azure.com managedclusters.containerservice.azure.com managedclustersagentpools.containerservice.azure.com bastionhosts.network.azure.com virtualnetworks.network.azure.com virtualnetworkssubnets.network.azure.com privateendpoints.network.azure.com fleetsmembers.containerservice.azure.com extensions.kubernetesconfiguration.azure.com

# Allow overriding the imagePullPolicy
Expand Down Expand Up @@ -333,7 +333,7 @@ create-management-cluster: $(KUSTOMIZE) $(ENVSUBST) $(KUBECTL) $(KIND) ## Create
./hack/create-custom-cloud-provider-config.sh

# Deploy CAPI
timeout --foreground 300 bash -c "until curl --retry $(CURL_RETRIES) -sSL https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.9.6/cluster-api-components.yaml | $(ENVSUBST) | $(KUBECTL) apply -f -; do sleep 5; done"
timeout --foreground 300 bash -c "until curl --retry $(CURL_RETRIES) -sSL https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.10.0-beta.1/cluster-api-components.yaml | $(ENVSUBST) | $(KUBECTL) apply -f -; do sleep 5; done"

# Deploy CAAPH
timeout --foreground 300 bash -c "until curl --retry $(CURL_RETRIES) -sSL https://github.com/kubernetes-sigs/cluster-api-addon-provider-helm/releases/download/v0.2.5/addon-components.yaml | $(ENVSUBST) | $(KUBECTL) apply -f -; do sleep 5; done"
Expand Down
4 changes: 2 additions & 2 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ settings = {
"deploy_cert_manager": True,
"preload_images_for_kind": True,
"kind_cluster_name": "capz",
"capi_version": "v1.9.6",
"capi_version": "v1.10.0-beta.1",
"caaph_version": "v0.2.5",
"cert_manager_version": "v1.16.4",
"kubernetes_version": "v1.30.2",
Expand Down Expand Up @@ -173,7 +173,7 @@ def validate_auth():

tilt_helper_dockerfile_header = """
# Tilt image
FROM golang:1.22 AS tilt-helper
FROM golang:1.23 AS tilt-helper
# Support live reloading with Tilt
RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh && \
wget --output-document /start.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/start.sh && \
Expand Down
89 changes: 51 additions & 38 deletions api/v1beta1/azurecluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1beta1

import (
"context"
"reflect"

apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -31,129 +32,141 @@ import (

// SetupWebhookWithManager sets up and registers the webhook with the manager.
func (c *AzureCluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
w := new(AzureClusterWebhook)
return ctrl.NewWebhookManagedBy(mgr).
For(c).
WithDefaulter(w).
WithValidator(w).
Complete()
}

// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azurecluster,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azureclusters,versions=v1beta1,name=validation.azurecluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-azurecluster,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azureclusters,versions=v1beta1,name=default.azurecluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1

var _ webhook.Validator = &AzureCluster{}
var _ webhook.Defaulter = &AzureCluster{}
// AzureClusterWebhook is a webhook for AzureCluster.
type AzureClusterWebhook struct{}

var (
_ webhook.CustomValidator = &AzureClusterWebhook{}
_ webhook.CustomDefaulter = &AzureClusterWebhook{}
)

// Default implements webhook.Defaulter so a webhook will be registered for the type.
func (c *AzureCluster) Default() {
func (*AzureClusterWebhook) Default(_ context.Context, obj runtime.Object) error {
c := obj.(*AzureCluster)
c.setDefaults()
return nil
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
func (c *AzureCluster) ValidateCreate() (admission.Warnings, error) {
func (*AzureClusterWebhook) ValidateCreate(_ context.Context, newObj runtime.Object) (admission.Warnings, error) {
c := newObj.(*AzureCluster)
return c.validateCluster(nil)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
func (c *AzureCluster) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) {
func (*AzureClusterWebhook) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
var allErrs field.ErrorList
old := oldRaw.(*AzureCluster)
oldAzureCluster := oldObj.(*AzureCluster)
newAzureCluster := newObj.(*AzureCluster)

if err := webhookutils.ValidateImmutable(
field.NewPath("spec", "resourceGroup"),
old.Spec.ResourceGroup,
c.Spec.ResourceGroup); err != nil {
oldAzureCluster.Spec.ResourceGroup,
newAzureCluster.Spec.ResourceGroup); err != nil {
allErrs = append(allErrs, err)
}

if err := webhookutils.ValidateImmutable(
field.NewPath("spec", "subscriptionID"),
old.Spec.SubscriptionID,
c.Spec.SubscriptionID); err != nil {
oldAzureCluster.Spec.SubscriptionID,
newAzureCluster.Spec.SubscriptionID); err != nil {
allErrs = append(allErrs, err)
}

if err := webhookutils.ValidateImmutable(
field.NewPath("spec", "location"),
old.Spec.Location,
c.Spec.Location); err != nil {
oldAzureCluster.Spec.Location,
newAzureCluster.Spec.Location); err != nil {
allErrs = append(allErrs, err)
}

if old.Spec.ControlPlaneEndpoint.Host != "" && c.Spec.ControlPlaneEndpoint.Host != old.Spec.ControlPlaneEndpoint.Host {
if oldAzureCluster.Spec.ControlPlaneEndpoint.Host != "" && newAzureCluster.Spec.ControlPlaneEndpoint.Host != oldAzureCluster.Spec.ControlPlaneEndpoint.Host {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "controlPlaneEndpoint", "host"),
c.Spec.ControlPlaneEndpoint.Host, "field is immutable"),
newAzureCluster.Spec.ControlPlaneEndpoint.Host, "field is immutable"),
)
}

if old.Spec.ControlPlaneEndpoint.Port != 0 && c.Spec.ControlPlaneEndpoint.Port != old.Spec.ControlPlaneEndpoint.Port {
if oldAzureCluster.Spec.ControlPlaneEndpoint.Port != 0 && newAzureCluster.Spec.ControlPlaneEndpoint.Port != oldAzureCluster.Spec.ControlPlaneEndpoint.Port {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "controlPlaneEndpoint", "port"),
c.Spec.ControlPlaneEndpoint.Port, "field is immutable"),
newAzureCluster.Spec.ControlPlaneEndpoint.Port, "field is immutable"),
)
}

if !reflect.DeepEqual(c.Spec.AzureEnvironment, old.Spec.AzureEnvironment) {
if !reflect.DeepEqual(newAzureCluster.Spec.AzureEnvironment, oldAzureCluster.Spec.AzureEnvironment) {
// The equality failure could be because of default mismatch between v1alpha3 and v1beta1. This happens because
// the new object `r` will have run through the default webhooks but the old object `old` would not have so.
// the new object `r` will have run through the default webhooks but the old object `oldAzureCluster` would not have so.
// This means if the old object was in v1alpha3, it would not get the new defaults set in v1beta1 resulting
// in object inequality. To workaround this, we set the v1beta1 defaults here so that the old object also gets
// the new defaults.
old.setAzureEnvironmentDefault()
oldAzureCluster.setAzureEnvironmentDefault()

// if it's still not equal, return error.
if !reflect.DeepEqual(c.Spec.AzureEnvironment, old.Spec.AzureEnvironment) {
if !reflect.DeepEqual(newAzureCluster.Spec.AzureEnvironment, oldAzureCluster.Spec.AzureEnvironment) {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "azureEnvironment"),
c.Spec.AzureEnvironment, "field is immutable"),
newAzureCluster.Spec.AzureEnvironment, "field is immutable"),
)
}
}

if err := webhookutils.ValidateImmutable(
field.NewPath("spec", "networkSpec", "privateDNSZoneName"),
old.Spec.NetworkSpec.PrivateDNSZoneName,
c.Spec.NetworkSpec.PrivateDNSZoneName); err != nil {
oldAzureCluster.Spec.NetworkSpec.PrivateDNSZoneName,
newAzureCluster.Spec.NetworkSpec.PrivateDNSZoneName); err != nil {
allErrs = append(allErrs, err)
}

if err := webhookutils.ValidateImmutable(
field.NewPath("spec", "networkSpec", "privateDNSZoneResourceGroup"),
old.Spec.NetworkSpec.PrivateDNSZoneResourceGroup,
c.Spec.NetworkSpec.PrivateDNSZoneResourceGroup); err != nil {
oldAzureCluster.Spec.NetworkSpec.PrivateDNSZoneResourceGroup,
newAzureCluster.Spec.NetworkSpec.PrivateDNSZoneResourceGroup); err != nil {
allErrs = append(allErrs, err)
}

// Allow enabling azure bastion but avoid disabling it.
if old.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(old.Spec.BastionSpec.AzureBastion, c.Spec.BastionSpec.AzureBastion) {
if oldAzureCluster.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(oldAzureCluster.Spec.BastionSpec.AzureBastion, newAzureCluster.Spec.BastionSpec.AzureBastion) {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "bastionSpec", "azureBastion"),
c.Spec.BastionSpec.AzureBastion, "azure bastion cannot be removed from a cluster"),
newAzureCluster.Spec.BastionSpec.AzureBastion, "azure bastion cannot be removed from a cluster"),
)
}

if err := webhookutils.ValidateImmutable(
field.NewPath("spec", "networkSpec", "controlPlaneOutboundLB"),
old.Spec.NetworkSpec.ControlPlaneOutboundLB,
c.Spec.NetworkSpec.ControlPlaneOutboundLB); err != nil {
oldAzureCluster.Spec.NetworkSpec.ControlPlaneOutboundLB,
newAzureCluster.Spec.NetworkSpec.ControlPlaneOutboundLB); err != nil {
allErrs = append(allErrs, err)
}

allErrs = append(allErrs, c.validateSubnetUpdate(old)...)
allErrs = append(allErrs, newAzureCluster.validateSubnetUpdate(oldAzureCluster)...)

if len(allErrs) == 0 {
return c.validateCluster(old)
return newAzureCluster.validateCluster(oldAzureCluster)
}

return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureClusterKind).GroupKind(), c.Name, allErrs)
return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureClusterKind).GroupKind(), newAzureCluster.Name, allErrs)
}

// validateSubnetUpdate validates a ClusterSpec.NetworkSpec.Subnets for immutability.
func (c *AzureCluster) validateSubnetUpdate(old *AzureCluster) field.ErrorList {
func (c *AzureCluster) validateSubnetUpdate(oldAzureCluster *AzureCluster) field.ErrorList {
var allErrs field.ErrorList

oldSubnetMap := make(map[string]SubnetSpec, len(old.Spec.NetworkSpec.Subnets))
oldSubnetIndex := make(map[string]int, len(old.Spec.NetworkSpec.Subnets))
for i, subnet := range old.Spec.NetworkSpec.Subnets {
oldSubnetMap := make(map[string]SubnetSpec, len(oldAzureCluster.Spec.NetworkSpec.Subnets))
oldSubnetIndex := make(map[string]int, len(oldAzureCluster.Spec.NetworkSpec.Subnets))
for i, subnet := range oldAzureCluster.Spec.NetworkSpec.Subnets {
oldSubnetMap[subnet.Name] = subnet
oldSubnetIndex[subnet.Name] = i
}
Expand All @@ -165,7 +178,7 @@ func (c *AzureCluster) validateSubnetUpdate(old *AzureCluster) field.ErrorList {
// This technically allows the cidr block to be modified in the brief
// moments before the Vnet is created (because the tags haven't been
// set yet) but once the Vnet has been created it becomes immutable.
if old.Spec.NetworkSpec.Vnet.Tags.HasOwned(old.Name) && !reflect.DeepEqual(subnet.CIDRBlocks, oldSubnet.CIDRBlocks) {
if oldAzureCluster.Spec.NetworkSpec.Vnet.Tags.HasOwned(oldAzureCluster.Name) && !reflect.DeepEqual(subnet.CIDRBlocks, oldSubnet.CIDRBlocks) {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "networkSpec", "subnets").Index(oldSubnetIndex[subnet.Name]).Child("CIDRBlocks"),
c.Spec.NetworkSpec.Subnets[i].CIDRBlocks, "field is immutable"),
Expand Down Expand Up @@ -196,6 +209,6 @@ func (c *AzureCluster) validateSubnetUpdate(old *AzureCluster) field.ErrorList {
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (c *AzureCluster) ValidateDelete() (admission.Warnings, error) {
func (*AzureClusterWebhook) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
return nil, nil
}
9 changes: 7 additions & 2 deletions api/v1beta1/azurecluster_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1beta1

import (
"context"
"testing"

. "github.com/onsi/gomega"
Expand Down Expand Up @@ -108,7 +109,9 @@ func TestAzureCluster_ValidateCreate(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
_, err := tc.cluster.ValidateCreate()
// create a new dummy AzureClusterWebhook
w := new(AzureClusterWebhook)
_, err := w.ValidateCreate(context.TODO(), tc.cluster)
if tc.wantErr {
g.Expect(err).To(HaveOccurred())
} else {
Expand Down Expand Up @@ -344,7 +347,9 @@ func TestAzureCluster_ValidateUpdate(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
g := NewWithT(t)
_, err := tc.cluster.ValidateUpdate(tc.oldCluster)
// create a new dummy AzureClusterWebhook
w := new(AzureClusterWebhook)
_, err := w.ValidateUpdate(context.TODO(), tc.oldCluster, tc.cluster)
if tc.wantErr {
g.Expect(err).To(HaveOccurred())
} else {
Expand Down
28 changes: 19 additions & 9 deletions api/v1beta1/azureclusteridentity_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v1beta1

import (
"context"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
Expand All @@ -29,37 +31,45 @@ import (

// SetupWebhookWithManager sets up and registers the webhook with the manager.
func (c *AzureClusterIdentity) SetupWebhookWithManager(mgr ctrl.Manager) error {
w := new(azureClusterIdentityWebhook)
return ctrl.NewWebhookManagedBy(mgr).
For(c).
WithValidator(w).
Complete()
}

// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azureclusteridentity,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azureclusteridentities,versions=v1beta1,name=validation.azureclusteridentity.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1

var _ webhook.Validator = &AzureClusterIdentity{}
type azureClusterIdentityWebhook struct{}

var (
_ webhook.CustomValidator = &azureClusterIdentityWebhook{}
)

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
func (c *AzureClusterIdentity) ValidateCreate() (admission.Warnings, error) {
func (*azureClusterIdentityWebhook) ValidateCreate(_ context.Context, newObj runtime.Object) (admission.Warnings, error) {
c := newObj.(*AzureClusterIdentity)
return c.validateClusterIdentity()
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
func (c *AzureClusterIdentity) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) {
func (*azureClusterIdentityWebhook) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
var allErrs field.ErrorList
old := oldRaw.(*AzureClusterIdentity)
oldAzureClusterIdentity := oldObj.(*AzureClusterIdentity)
newAzureClusterIdentity := newObj.(*AzureClusterIdentity)
if err := webhookutils.ValidateImmutable(
field.NewPath("Spec", "Type"),
old.Spec.Type,
c.Spec.Type); err != nil {
oldAzureClusterIdentity.Spec.Type,
newAzureClusterIdentity.Spec.Type); err != nil {
allErrs = append(allErrs, err)
}
if len(allErrs) == 0 {
return c.validateClusterIdentity()
return newAzureClusterIdentity.validateClusterIdentity()
}
return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureClusterIdentityKind).GroupKind(), c.Name, allErrs)
return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureClusterIdentityKind).GroupKind(), newAzureClusterIdentity.Name, allErrs)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (c *AzureClusterIdentity) ValidateDelete() (admission.Warnings, error) {
func (*azureClusterIdentityWebhook) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
return nil, nil
}
Loading
Loading