diff --git a/Makefile b/Makefile index 73261581a96..6291603d436 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ WEBHOOK_ROOT ?= $(MANIFEST_ROOT)/webhook RBAC_ROOT ?= $(MANIFEST_ROOT)/rbac ASO_CRDS_PATH := $(MANIFEST_ROOT)/aso/crds.yaml ASO_VERSION := v2.3.0 -ASO_CRDS := resourcegroups.resources.azure.com +ASO_CRDS := resourcegroups.resources.azure.com natgateways.network.azure.com # Allow overriding the imagePullPolicy PULL_POLICY ?= Always diff --git a/azure/scope/cluster.go b/azure/scope/cluster.go index ebdea04506c..3564c337bcc 100644 --- a/azure/scope/cluster.go +++ b/azure/scope/cluster.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701" "hash/fnv" "sort" "strconv" @@ -326,9 +327,9 @@ func (s *ClusterScope) RouteTableSpecs() []azure.ResourceSpecGetter { } // NatGatewaySpecs returns the node NAT gateway. -func (s *ClusterScope) NatGatewaySpecs() []azure.ResourceSpecGetter { +func (s *ClusterScope) NatGatewaySpecs() []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway] { natGatewaySet := make(map[string]struct{}) - var natGateways []azure.ResourceSpecGetter + var natGateways []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway] // We ignore the control plane NAT gateway, as we will always use a LB to enable egress on the control plane. for _, subnet := range s.NodeSubnets() { diff --git a/azure/scope/cluster_test.go b/azure/scope/cluster_test.go index b726a93f1b2..527595fd920 100644 --- a/azure/scope/cluster_test.go +++ b/azure/scope/cluster_test.go @@ -19,6 +19,8 @@ package scope import ( "context" "fmt" + asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "reflect" "strings" "testing" @@ -63,6 +65,25 @@ func specArrayToString(specs []azure.ResourceSpecGetter) string { return sb.String() } +func specToStringForAsoResource[T genruntime.MetaObject](spec azure.ASOResourceSpecGetter[T]) string { + var sb strings.Builder + sb.WriteString("{ ") + sb.WriteString(fmt.Sprintf("%+v ", spec)) + sb.WriteString("}") + return sb.String() +} + +func specArrayToStringForAsoResource[T genruntime.MetaObject](specs []azure.ASOResourceSpecGetter[T]) string { + var sb strings.Builder + sb.WriteString("[\n") + for _, spec := range specs { + sb.WriteString(fmt.Sprintf("\t%+v\n", specToStringForAsoResource(spec))) + } + sb.WriteString("]") + + return sb.String() +} + func TestAPIServerHost(t *testing.T) { fakeSubscriptionID := "123" @@ -867,7 +888,7 @@ func TestNatGatewaySpecs(t *testing.T) { tests := []struct { name string clusterScope ClusterScope - want []azure.ResourceSpecGetter + want []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway] }{ { name: "returns nil if no subnets are specified", @@ -929,7 +950,7 @@ func TestNatGatewaySpecs(t *testing.T) { }, cache: &ClusterCache{}, }, - want: []azure.ResourceSpecGetter{ + want: []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway]{ &natgateways.NatGatewaySpec{ Name: "fake-nat-gateway-1", ResourceGroup: "my-rg", @@ -1007,7 +1028,7 @@ func TestNatGatewaySpecs(t *testing.T) { }, cache: &ClusterCache{}, }, - want: []azure.ResourceSpecGetter{ + want: []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway]{ &natgateways.NatGatewaySpec{ Name: "fake-nat-gateway-1", ResourceGroup: "my-rg", @@ -1084,7 +1105,7 @@ func TestNatGatewaySpecs(t *testing.T) { }, cache: &ClusterCache{}, }, - want: []azure.ResourceSpecGetter{ + want: []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway]{ &natgateways.NatGatewaySpec{ Name: "fake-nat-gateway-1", ResourceGroup: "my-rg", @@ -1105,7 +1126,79 @@ func TestNatGatewaySpecs(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := tt.clusterScope.NatGatewaySpecs(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NatGatewaySpecs() = %s, want %s", specArrayToString(got), specArrayToString(tt.want)) + t.Errorf("NatGatewaySpecs() = %s, want %s", specArrayToStringForAsoResource(got), specArrayToStringForAsoResource(tt.want)) + } + }) + } +} + +func TestSetNatGatewayIDInSubnets(t *testing.T) { + tests := []struct { + name string + clusterScope ClusterScope + asoNatgateway *asonetworkv1.NatGateway + }{ + { + name: "sets nat gateway id in the matching subnet", + clusterScope: ClusterScope{ + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + }, + }, + AzureCluster: &infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + Subnets: infrav1.Subnets{ + { + SubnetClassSpec: infrav1.SubnetClassSpec{ + Name: "fake-subnet-1", + }, + NatGateway: infrav1.NatGateway{ + NatGatewayClassSpec: infrav1.NatGatewayClassSpec{ + Name: "fake-nat-gateway-1", + }, + }, + }, + { + SubnetClassSpec: infrav1.SubnetClassSpec{ + Name: "fake-subnet-2", + }, + NatGateway: infrav1.NatGateway{ + NatGatewayClassSpec: infrav1.NatGatewayClassSpec{ + Name: "fake-nat-gateway-2", + }, + }, + }, + }, + }, + }, + }, + cache: &ClusterCache{}, + }, + asoNatgateway: &asonetworkv1.NatGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-nat-gateway-1", + }, + Status: asonetworkv1.NatGateway_STATUS{ + Id: ptr.To("dummy-id-1"), + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + t.Parallel() + tt.clusterScope.SetNatGatewayIDInSubnets(tt.asoNatgateway.Name, *tt.asoNatgateway.Status.Id) + for _, subnet := range tt.clusterScope.AzureCluster.Spec.NetworkSpec.Subnets { + if subnet.NatGateway.Name == tt.asoNatgateway.Name { + g.Expect(subnet.NatGateway.ID).To(Equal(*tt.asoNatgateway.Status.Id)) + } else { + g.Expect(subnet.NatGateway.ID).To(Equal("")) + } } }) } diff --git a/azure/services/natgateways/client.go b/azure/services/natgateways/client.go deleted file mode 100644 index 3f3599b0557..00000000000 --- a/azure/services/natgateways/client.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2021 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 natgateways - -import ( - "context" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" - "github.com/pkg/errors" - "sigs.k8s.io/cluster-api-provider-azure/azure" - "sigs.k8s.io/cluster-api-provider-azure/azure/services/async" - "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" - "sigs.k8s.io/cluster-api-provider-azure/util/tele" -) - -// azureClient contains the Azure go-sdk Client. -type azureClient struct { - natgateways *armnetwork.NatGatewaysClient -} - -// newClient creates a new nat gateways client from an authorizer. -func newClient(auth azure.Authorizer) (*azureClient, error) { - opts, err := azure.ARMClientOptions(auth.CloudEnvironment()) - if err != nil { - return nil, errors.Wrap(err, "failed to create natgateways client options") - } - factory, err := armnetwork.NewClientFactory(auth.SubscriptionID(), auth.Token(), opts) - if err != nil { - return nil, errors.Wrap(err, "failed to create armnetwork client factory") - } - return &azureClient{factory.NewNatGatewaysClient()}, nil -} - -// Get gets the specified nat gateway. -func (ac *azureClient) Get(ctx context.Context, spec azure.ResourceSpecGetter) (result interface{}, err error) { - ctx, _, done := tele.StartSpanWithLogger(ctx, "natgateways.azureClient.Get") - defer done() - - resp, err := ac.natgateways.Get(ctx, spec.ResourceGroupName(), spec.ResourceName(), nil) - if err != nil { - return nil, err - } - return resp.NatGateway, nil -} - -// CreateOrUpdateAsync creates or updates a Nat Gateway asynchronously. -// It sends a PUT request to Azure and if accepted without error, the func will return a Poller which can be used to track the ongoing -// progress of the operation. -func (ac *azureClient) CreateOrUpdateAsync(ctx context.Context, spec azure.ResourceSpecGetter, resumeToken string, parameters interface{}) (result interface{}, poller *runtime.Poller[armnetwork.NatGatewaysClientCreateOrUpdateResponse], err error) { - ctx, log, done := tele.StartSpanWithLogger(ctx, "natgateways.azureClient.CreateOrUpdateAsync") - defer done() - - natGateway, ok := parameters.(armnetwork.NatGateway) - if !ok && parameters != nil { - return nil, nil, errors.Errorf("%T is not an armnetwork.NatGateway", parameters) - } - - opts := &armnetwork.NatGatewaysClientBeginCreateOrUpdateOptions{ResumeToken: resumeToken} - log.V(4).Info("sending request", "resumeToken", resumeToken) - poller, err = ac.natgateways.BeginCreateOrUpdate(ctx, spec.ResourceGroupName(), spec.ResourceName(), natGateway, opts) - if err != nil { - return nil, nil, err - } - - ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureCallTimeout) - defer cancel() - - pollOpts := &runtime.PollUntilDoneOptions{Frequency: async.DefaultPollerFrequency} - resp, err := poller.PollUntilDone(ctx, pollOpts) - if err != nil { - // if an error occurs, return the poller. - // this means the long-running operation didn't finish in the specified timeout. - return nil, poller, err - } - - // if the operation completed, return a nil poller - return resp.NatGateway, nil, err -} - -// DeleteAsync deletes a Nat Gateway asynchronously. DeleteAsync sends a DELETE -// request to Azure and if accepted without error, the func will return a Poller which can be used to track the ongoing -// progress of the operation. -func (ac *azureClient) DeleteAsync(ctx context.Context, spec azure.ResourceSpecGetter, resumeToken string) (poller *runtime.Poller[armnetwork.NatGatewaysClientDeleteResponse], err error) { - ctx, log, done := tele.StartSpanWithLogger(ctx, "natgateways.azureClient.DeleteAsync") - defer done() - - opts := &armnetwork.NatGatewaysClientBeginDeleteOptions{ResumeToken: resumeToken} - log.V(4).Info("sending request", "resumeToken", resumeToken) - poller, err = ac.natgateways.BeginDelete(ctx, spec.ResourceGroupName(), spec.ResourceName(), opts) - if err != nil { - return nil, err - } - - ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureCallTimeout) - defer cancel() - - pollOpts := &runtime.PollUntilDoneOptions{Frequency: async.DefaultPollerFrequency} - _, err = poller.PollUntilDone(ctx, pollOpts) - if err != nil { - // if an error occurs, return the Poller. - // this means the long-running operation didn't finish in the specified timeout. - return poller, err - } - - // if the operation completed, return a nil poller. - return nil, err -} diff --git a/azure/services/natgateways/mock_natgateways/doc.go b/azure/services/natgateways/mock_natgateways/doc.go deleted file mode 100644 index 96bc5cd0e8c..00000000000 --- a/azure/services/natgateways/mock_natgateways/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2019 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. -*/ - -// Run go generate to regenerate this mock. -// -//go:generate ../../../../hack/tools/bin/mockgen -destination natgateways_mock.go -package mock_natgateways -source ../natgateways.go NatGatewayScope -//go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt natgateways_mock.go > _natgateways_mock.go && mv _natgateways_mock.go natgateways_mock.go" -package mock_natgateways diff --git a/azure/services/natgateways/mock_natgateways/natgateways_mock.go b/azure/services/natgateways/mock_natgateways/natgateways_mock.go deleted file mode 100644 index fe774560a79..00000000000 --- a/azure/services/natgateways/mock_natgateways/natgateways_mock.go +++ /dev/null @@ -1,647 +0,0 @@ -/* -Copyright 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. -*/ - -// Code generated by MockGen. DO NOT EDIT. -// Source: ../natgateways.go -// -// Generated by this command: -// -// mockgen -destination natgateways_mock.go -package mock_natgateways -source ../natgateways.go NatGatewayScope -// -// Package mock_natgateways is a generated GoMock package. -package mock_natgateways - -import ( - reflect "reflect" - - azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore" - autorest "github.com/Azure/go-autorest/autorest" - gomock "go.uber.org/mock/gomock" - v1beta1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" - azure "sigs.k8s.io/cluster-api-provider-azure/azure" - v1beta10 "sigs.k8s.io/cluster-api/api/v1beta1" -) - -// MockNatGatewayScope is a mock of NatGatewayScope interface. -type MockNatGatewayScope struct { - ctrl *gomock.Controller - recorder *MockNatGatewayScopeMockRecorder -} - -// MockNatGatewayScopeMockRecorder is the mock recorder for MockNatGatewayScope. -type MockNatGatewayScopeMockRecorder struct { - mock *MockNatGatewayScope -} - -// NewMockNatGatewayScope creates a new mock instance. -func NewMockNatGatewayScope(ctrl *gomock.Controller) *MockNatGatewayScope { - mock := &MockNatGatewayScope{ctrl: ctrl} - mock.recorder = &MockNatGatewayScopeMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockNatGatewayScope) EXPECT() *MockNatGatewayScopeMockRecorder { - return m.recorder -} - -// APIServerLB mocks base method. -func (m *MockNatGatewayScope) APIServerLB() *v1beta1.LoadBalancerSpec { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "APIServerLB") - ret0, _ := ret[0].(*v1beta1.LoadBalancerSpec) - return ret0 -} - -// APIServerLB indicates an expected call of APIServerLB. -func (mr *MockNatGatewayScopeMockRecorder) APIServerLB() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "APIServerLB", reflect.TypeOf((*MockNatGatewayScope)(nil).APIServerLB)) -} - -// APIServerLBName mocks base method. -func (m *MockNatGatewayScope) APIServerLBName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "APIServerLBName") - ret0, _ := ret[0].(string) - return ret0 -} - -// APIServerLBName indicates an expected call of APIServerLBName. -func (mr *MockNatGatewayScopeMockRecorder) APIServerLBName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "APIServerLBName", reflect.TypeOf((*MockNatGatewayScope)(nil).APIServerLBName)) -} - -// APIServerLBPoolName mocks base method. -func (m *MockNatGatewayScope) APIServerLBPoolName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "APIServerLBPoolName") - ret0, _ := ret[0].(string) - return ret0 -} - -// APIServerLBPoolName indicates an expected call of APIServerLBPoolName. -func (mr *MockNatGatewayScopeMockRecorder) APIServerLBPoolName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "APIServerLBPoolName", reflect.TypeOf((*MockNatGatewayScope)(nil).APIServerLBPoolName)) -} - -// AdditionalTags mocks base method. -func (m *MockNatGatewayScope) AdditionalTags() v1beta1.Tags { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AdditionalTags") - ret0, _ := ret[0].(v1beta1.Tags) - return ret0 -} - -// AdditionalTags indicates an expected call of AdditionalTags. -func (mr *MockNatGatewayScopeMockRecorder) AdditionalTags() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdditionalTags", reflect.TypeOf((*MockNatGatewayScope)(nil).AdditionalTags)) -} - -// Authorizer mocks base method. -func (m *MockNatGatewayScope) Authorizer() autorest.Authorizer { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Authorizer") - ret0, _ := ret[0].(autorest.Authorizer) - return ret0 -} - -// Authorizer indicates an expected call of Authorizer. -func (mr *MockNatGatewayScopeMockRecorder) Authorizer() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authorizer", reflect.TypeOf((*MockNatGatewayScope)(nil).Authorizer)) -} - -// AvailabilitySetEnabled mocks base method. -func (m *MockNatGatewayScope) AvailabilitySetEnabled() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AvailabilitySetEnabled") - ret0, _ := ret[0].(bool) - return ret0 -} - -// AvailabilitySetEnabled indicates an expected call of AvailabilitySetEnabled. -func (mr *MockNatGatewayScopeMockRecorder) AvailabilitySetEnabled() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AvailabilitySetEnabled", reflect.TypeOf((*MockNatGatewayScope)(nil).AvailabilitySetEnabled)) -} - -// BaseURI mocks base method. -func (m *MockNatGatewayScope) BaseURI() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BaseURI") - ret0, _ := ret[0].(string) - return ret0 -} - -// BaseURI indicates an expected call of BaseURI. -func (mr *MockNatGatewayScopeMockRecorder) BaseURI() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BaseURI", reflect.TypeOf((*MockNatGatewayScope)(nil).BaseURI)) -} - -// ClientID mocks base method. -func (m *MockNatGatewayScope) ClientID() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientID") - ret0, _ := ret[0].(string) - return ret0 -} - -// ClientID indicates an expected call of ClientID. -func (mr *MockNatGatewayScopeMockRecorder) ClientID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientID", reflect.TypeOf((*MockNatGatewayScope)(nil).ClientID)) -} - -// ClientSecret mocks base method. -func (m *MockNatGatewayScope) ClientSecret() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientSecret") - ret0, _ := ret[0].(string) - return ret0 -} - -// ClientSecret indicates an expected call of ClientSecret. -func (mr *MockNatGatewayScopeMockRecorder) ClientSecret() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientSecret", reflect.TypeOf((*MockNatGatewayScope)(nil).ClientSecret)) -} - -// CloudEnvironment mocks base method. -func (m *MockNatGatewayScope) CloudEnvironment() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CloudEnvironment") - ret0, _ := ret[0].(string) - return ret0 -} - -// CloudEnvironment indicates an expected call of CloudEnvironment. -func (mr *MockNatGatewayScopeMockRecorder) CloudEnvironment() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloudEnvironment", reflect.TypeOf((*MockNatGatewayScope)(nil).CloudEnvironment)) -} - -// CloudProviderConfigOverrides mocks base method. -func (m *MockNatGatewayScope) CloudProviderConfigOverrides() *v1beta1.CloudProviderConfigOverrides { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CloudProviderConfigOverrides") - ret0, _ := ret[0].(*v1beta1.CloudProviderConfigOverrides) - return ret0 -} - -// CloudProviderConfigOverrides indicates an expected call of CloudProviderConfigOverrides. -func (mr *MockNatGatewayScopeMockRecorder) CloudProviderConfigOverrides() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloudProviderConfigOverrides", reflect.TypeOf((*MockNatGatewayScope)(nil).CloudProviderConfigOverrides)) -} - -// ClusterName mocks base method. -func (m *MockNatGatewayScope) ClusterName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClusterName") - ret0, _ := ret[0].(string) - return ret0 -} - -// ClusterName indicates an expected call of ClusterName. -func (mr *MockNatGatewayScopeMockRecorder) ClusterName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterName", reflect.TypeOf((*MockNatGatewayScope)(nil).ClusterName)) -} - -// ControlPlaneRouteTable mocks base method. -func (m *MockNatGatewayScope) ControlPlaneRouteTable() v1beta1.RouteTable { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ControlPlaneRouteTable") - ret0, _ := ret[0].(v1beta1.RouteTable) - return ret0 -} - -// ControlPlaneRouteTable indicates an expected call of ControlPlaneRouteTable. -func (mr *MockNatGatewayScopeMockRecorder) ControlPlaneRouteTable() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControlPlaneRouteTable", reflect.TypeOf((*MockNatGatewayScope)(nil).ControlPlaneRouteTable)) -} - -// ControlPlaneSubnet mocks base method. -func (m *MockNatGatewayScope) ControlPlaneSubnet() v1beta1.SubnetSpec { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ControlPlaneSubnet") - ret0, _ := ret[0].(v1beta1.SubnetSpec) - return ret0 -} - -// ControlPlaneSubnet indicates an expected call of ControlPlaneSubnet. -func (mr *MockNatGatewayScopeMockRecorder) ControlPlaneSubnet() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControlPlaneSubnet", reflect.TypeOf((*MockNatGatewayScope)(nil).ControlPlaneSubnet)) -} - -// DeleteLongRunningOperationState mocks base method. -func (m *MockNatGatewayScope) DeleteLongRunningOperationState(arg0, arg1, arg2 string) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "DeleteLongRunningOperationState", arg0, arg1, arg2) -} - -// DeleteLongRunningOperationState indicates an expected call of DeleteLongRunningOperationState. -func (mr *MockNatGatewayScopeMockRecorder) DeleteLongRunningOperationState(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockNatGatewayScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) -} - -// ExtendedLocation mocks base method. -func (m *MockNatGatewayScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExtendedLocation") - ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) - return ret0 -} - -// ExtendedLocation indicates an expected call of ExtendedLocation. -func (mr *MockNatGatewayScopeMockRecorder) ExtendedLocation() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockNatGatewayScope)(nil).ExtendedLocation)) -} - -// ExtendedLocationName mocks base method. -func (m *MockNatGatewayScope) ExtendedLocationName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExtendedLocationName") - ret0, _ := ret[0].(string) - return ret0 -} - -// ExtendedLocationName indicates an expected call of ExtendedLocationName. -func (mr *MockNatGatewayScopeMockRecorder) ExtendedLocationName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockNatGatewayScope)(nil).ExtendedLocationName)) -} - -// ExtendedLocationType mocks base method. -func (m *MockNatGatewayScope) ExtendedLocationType() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExtendedLocationType") - ret0, _ := ret[0].(string) - return ret0 -} - -// ExtendedLocationType indicates an expected call of ExtendedLocationType. -func (mr *MockNatGatewayScopeMockRecorder) ExtendedLocationType() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockNatGatewayScope)(nil).ExtendedLocationType)) -} - -// FailureDomains mocks base method. -func (m *MockNatGatewayScope) FailureDomains() []*string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FailureDomains") - ret0, _ := ret[0].([]*string) - return ret0 -} - -// FailureDomains indicates an expected call of FailureDomains. -func (mr *MockNatGatewayScopeMockRecorder) FailureDomains() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FailureDomains", reflect.TypeOf((*MockNatGatewayScope)(nil).FailureDomains)) -} - -// GetLongRunningOperationState mocks base method. -func (m *MockNatGatewayScope) GetLongRunningOperationState(arg0, arg1, arg2 string) *v1beta1.Future { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLongRunningOperationState", arg0, arg1, arg2) - ret0, _ := ret[0].(*v1beta1.Future) - return ret0 -} - -// GetLongRunningOperationState indicates an expected call of GetLongRunningOperationState. -func (mr *MockNatGatewayScopeMockRecorder) GetLongRunningOperationState(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLongRunningOperationState", reflect.TypeOf((*MockNatGatewayScope)(nil).GetLongRunningOperationState), arg0, arg1, arg2) -} - -// GetPrivateDNSZoneName mocks base method. -func (m *MockNatGatewayScope) GetPrivateDNSZoneName() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPrivateDNSZoneName") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetPrivateDNSZoneName indicates an expected call of GetPrivateDNSZoneName. -func (mr *MockNatGatewayScopeMockRecorder) GetPrivateDNSZoneName() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateDNSZoneName", reflect.TypeOf((*MockNatGatewayScope)(nil).GetPrivateDNSZoneName)) -} - -// HashKey mocks base method. -func (m *MockNatGatewayScope) HashKey() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HashKey") - ret0, _ := ret[0].(string) - return ret0 -} - -// HashKey indicates an expected call of HashKey. -func (mr *MockNatGatewayScopeMockRecorder) HashKey() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashKey", reflect.TypeOf((*MockNatGatewayScope)(nil).HashKey)) -} - -// IsAPIServerPrivate mocks base method. -func (m *MockNatGatewayScope) IsAPIServerPrivate() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsAPIServerPrivate") - ret0, _ := ret[0].(bool) - return ret0 -} - -// IsAPIServerPrivate indicates an expected call of IsAPIServerPrivate. -func (mr *MockNatGatewayScopeMockRecorder) IsAPIServerPrivate() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAPIServerPrivate", reflect.TypeOf((*MockNatGatewayScope)(nil).IsAPIServerPrivate)) -} - -// IsIPv6Enabled mocks base method. -func (m *MockNatGatewayScope) IsIPv6Enabled() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsIPv6Enabled") - ret0, _ := ret[0].(bool) - return ret0 -} - -// IsIPv6Enabled indicates an expected call of IsIPv6Enabled. -func (mr *MockNatGatewayScopeMockRecorder) IsIPv6Enabled() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsIPv6Enabled", reflect.TypeOf((*MockNatGatewayScope)(nil).IsIPv6Enabled)) -} - -// IsVnetManaged mocks base method. -func (m *MockNatGatewayScope) IsVnetManaged() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsVnetManaged") - ret0, _ := ret[0].(bool) - return ret0 -} - -// IsVnetManaged indicates an expected call of IsVnetManaged. -func (mr *MockNatGatewayScopeMockRecorder) IsVnetManaged() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsVnetManaged", reflect.TypeOf((*MockNatGatewayScope)(nil).IsVnetManaged)) -} - -// Location mocks base method. -func (m *MockNatGatewayScope) Location() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Location") - ret0, _ := ret[0].(string) - return ret0 -} - -// Location indicates an expected call of Location. -func (mr *MockNatGatewayScopeMockRecorder) Location() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Location", reflect.TypeOf((*MockNatGatewayScope)(nil).Location)) -} - -// NatGatewaySpecs mocks base method. -func (m *MockNatGatewayScope) NatGatewaySpecs() []azure.ResourceSpecGetter { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NatGatewaySpecs") - ret0, _ := ret[0].([]azure.ResourceSpecGetter) - return ret0 -} - -// NatGatewaySpecs indicates an expected call of NatGatewaySpecs. -func (mr *MockNatGatewayScopeMockRecorder) NatGatewaySpecs() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NatGatewaySpecs", reflect.TypeOf((*MockNatGatewayScope)(nil).NatGatewaySpecs)) -} - -// NodeSubnets mocks base method. -func (m *MockNatGatewayScope) NodeSubnets() []v1beta1.SubnetSpec { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NodeSubnets") - ret0, _ := ret[0].([]v1beta1.SubnetSpec) - return ret0 -} - -// NodeSubnets indicates an expected call of NodeSubnets. -func (mr *MockNatGatewayScopeMockRecorder) NodeSubnets() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeSubnets", reflect.TypeOf((*MockNatGatewayScope)(nil).NodeSubnets)) -} - -// OutboundLBName mocks base method. -func (m *MockNatGatewayScope) OutboundLBName(arg0 string) string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OutboundLBName", arg0) - ret0, _ := ret[0].(string) - return ret0 -} - -// OutboundLBName indicates an expected call of OutboundLBName. -func (mr *MockNatGatewayScopeMockRecorder) OutboundLBName(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutboundLBName", reflect.TypeOf((*MockNatGatewayScope)(nil).OutboundLBName), arg0) -} - -// OutboundPoolName mocks base method. -func (m *MockNatGatewayScope) OutboundPoolName(arg0 string) string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OutboundPoolName", arg0) - ret0, _ := ret[0].(string) - return ret0 -} - -// OutboundPoolName indicates an expected call of OutboundPoolName. -func (mr *MockNatGatewayScopeMockRecorder) OutboundPoolName(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutboundPoolName", reflect.TypeOf((*MockNatGatewayScope)(nil).OutboundPoolName), arg0) -} - -// ResourceGroup mocks base method. -func (m *MockNatGatewayScope) ResourceGroup() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResourceGroup") - ret0, _ := ret[0].(string) - return ret0 -} - -// ResourceGroup indicates an expected call of ResourceGroup. -func (mr *MockNatGatewayScopeMockRecorder) ResourceGroup() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceGroup", reflect.TypeOf((*MockNatGatewayScope)(nil).ResourceGroup)) -} - -// SetLongRunningOperationState mocks base method. -func (m *MockNatGatewayScope) SetLongRunningOperationState(arg0 *v1beta1.Future) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetLongRunningOperationState", arg0) -} - -// SetLongRunningOperationState indicates an expected call of SetLongRunningOperationState. -func (mr *MockNatGatewayScopeMockRecorder) SetLongRunningOperationState(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLongRunningOperationState", reflect.TypeOf((*MockNatGatewayScope)(nil).SetLongRunningOperationState), arg0) -} - -// SetNatGatewayIDInSubnets mocks base method. -func (m *MockNatGatewayScope) SetNatGatewayIDInSubnets(natGatewayName, natGatewayID string) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetNatGatewayIDInSubnets", natGatewayName, natGatewayID) -} - -// SetNatGatewayIDInSubnets indicates an expected call of SetNatGatewayIDInSubnets. -func (mr *MockNatGatewayScopeMockRecorder) SetNatGatewayIDInSubnets(natGatewayName, natGatewayID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNatGatewayIDInSubnets", reflect.TypeOf((*MockNatGatewayScope)(nil).SetNatGatewayIDInSubnets), natGatewayName, natGatewayID) -} - -// SetSubnet mocks base method. -func (m *MockNatGatewayScope) SetSubnet(arg0 v1beta1.SubnetSpec) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnet", arg0) -} - -// SetSubnet indicates an expected call of SetSubnet. -func (mr *MockNatGatewayScopeMockRecorder) SetSubnet(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockNatGatewayScope)(nil).SetSubnet), arg0) -} - -// Subnet mocks base method. -func (m *MockNatGatewayScope) Subnet(arg0 string) v1beta1.SubnetSpec { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Subnet", arg0) - ret0, _ := ret[0].(v1beta1.SubnetSpec) - return ret0 -} - -// Subnet indicates an expected call of Subnet. -func (mr *MockNatGatewayScopeMockRecorder) Subnet(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subnet", reflect.TypeOf((*MockNatGatewayScope)(nil).Subnet), arg0) -} - -// Subnets mocks base method. -func (m *MockNatGatewayScope) Subnets() v1beta1.Subnets { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Subnets") - ret0, _ := ret[0].(v1beta1.Subnets) - return ret0 -} - -// Subnets indicates an expected call of Subnets. -func (mr *MockNatGatewayScopeMockRecorder) Subnets() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subnets", reflect.TypeOf((*MockNatGatewayScope)(nil).Subnets)) -} - -// SubscriptionID mocks base method. -func (m *MockNatGatewayScope) SubscriptionID() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubscriptionID") - ret0, _ := ret[0].(string) - return ret0 -} - -// SubscriptionID indicates an expected call of SubscriptionID. -func (mr *MockNatGatewayScopeMockRecorder) SubscriptionID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscriptionID", reflect.TypeOf((*MockNatGatewayScope)(nil).SubscriptionID)) -} - -// TenantID mocks base method. -func (m *MockNatGatewayScope) TenantID() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TenantID") - ret0, _ := ret[0].(string) - return ret0 -} - -// TenantID indicates an expected call of TenantID. -func (mr *MockNatGatewayScopeMockRecorder) TenantID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TenantID", reflect.TypeOf((*MockNatGatewayScope)(nil).TenantID)) -} - -// Token mocks base method. -func (m *MockNatGatewayScope) Token() azcore.TokenCredential { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Token") - ret0, _ := ret[0].(azcore.TokenCredential) - return ret0 -} - -// Token indicates an expected call of Token. -func (mr *MockNatGatewayScopeMockRecorder) Token() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Token", reflect.TypeOf((*MockNatGatewayScope)(nil).Token)) -} - -// UpdateDeleteStatus mocks base method. -func (m *MockNatGatewayScope) UpdateDeleteStatus(arg0 v1beta10.ConditionType, arg1 string, arg2 error) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "UpdateDeleteStatus", arg0, arg1, arg2) -} - -// UpdateDeleteStatus indicates an expected call of UpdateDeleteStatus. -func (mr *MockNatGatewayScopeMockRecorder) UpdateDeleteStatus(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDeleteStatus", reflect.TypeOf((*MockNatGatewayScope)(nil).UpdateDeleteStatus), arg0, arg1, arg2) -} - -// UpdatePatchStatus mocks base method. -func (m *MockNatGatewayScope) UpdatePatchStatus(arg0 v1beta10.ConditionType, arg1 string, arg2 error) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "UpdatePatchStatus", arg0, arg1, arg2) -} - -// UpdatePatchStatus indicates an expected call of UpdatePatchStatus. -func (mr *MockNatGatewayScopeMockRecorder) UpdatePatchStatus(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePatchStatus", reflect.TypeOf((*MockNatGatewayScope)(nil).UpdatePatchStatus), arg0, arg1, arg2) -} - -// UpdatePutStatus mocks base method. -func (m *MockNatGatewayScope) UpdatePutStatus(arg0 v1beta10.ConditionType, arg1 string, arg2 error) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "UpdatePutStatus", arg0, arg1, arg2) -} - -// UpdatePutStatus indicates an expected call of UpdatePutStatus. -func (mr *MockNatGatewayScopeMockRecorder) UpdatePutStatus(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePutStatus", reflect.TypeOf((*MockNatGatewayScope)(nil).UpdatePutStatus), arg0, arg1, arg2) -} - -// Vnet mocks base method. -func (m *MockNatGatewayScope) Vnet() *v1beta1.VnetSpec { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Vnet") - ret0, _ := ret[0].(*v1beta1.VnetSpec) - return ret0 -} - -// Vnet indicates an expected call of Vnet. -func (mr *MockNatGatewayScopeMockRecorder) Vnet() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Vnet", reflect.TypeOf((*MockNatGatewayScope)(nil).Vnet)) -} diff --git a/azure/services/natgateways/natgateways.go b/azure/services/natgateways/natgateways.go index ea97fd83328..c0696d54220 100644 --- a/azure/services/natgateways/natgateways.go +++ b/azure/services/natgateways/natgateways.go @@ -17,15 +17,11 @@ limitations under the License. package natgateways import ( - "context" + asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701" + "sigs.k8s.io/cluster-api-provider-azure/azure/services/aso" - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" - "github.com/pkg/errors" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/azure" - "sigs.k8s.io/cluster-api-provider-azure/azure/services/async" - "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" - "sigs.k8s.io/cluster-api-provider-azure/util/tele" ) const serviceName = "natgateways" @@ -33,123 +29,31 @@ const serviceName = "natgateways" // NatGatewayScope defines the scope interface for NAT gateway service. type NatGatewayScope interface { azure.ClusterScoper - azure.AsyncStatusUpdater + aso.Scope SetNatGatewayIDInSubnets(natGatewayName string, natGatewayID string) - NatGatewaySpecs() []azure.ResourceSpecGetter + NatGatewaySpecs() []azure.ASOResourceSpecGetter[*asonetworkv1.NatGateway] } // Service provides operations on azure resources. type Service struct { Scope NatGatewayScope - async.Reconciler + *aso.Service[*asonetworkv1.NatGateway, NatGatewayScope] } // New creates a new service. -func New(scope NatGatewayScope) (*Service, error) { - client, err := newClient(scope) - if err != nil { - return nil, err +func New(scope NatGatewayScope) *Service { + svc := aso.NewService[*asonetworkv1.NatGateway, NatGatewayScope](serviceName, scope) + svc.Specs = scope.NatGatewaySpecs() + svc.ConditionType = infrav1.NATGatewaysReadyCondition + svc.PostCreateOrUpdateResourceHook = func(s NatGatewayScope, result *asonetworkv1.NatGateway, err error) { + // Carrying over the while migrating to ASO Framework. + // TODO: ideally we wouldn't need to set the subnet spec based on the result of the create operation + // Fetch the Nat Gateway ID from the aso resource + // TODO @nawazkh: check the diff b/w result.Name and result.Status.Name + s.SetNatGatewayIDInSubnets(result.Name, *result.Status.Id) } return &Service{ - Scope: scope, - Reconciler: async.New[armnetwork.NatGatewaysClientCreateOrUpdateResponse, - armnetwork.NatGatewaysClientDeleteResponse](scope, client, client), - }, nil -} - -// Name returns the service name. -func (s *Service) Name() string { - return serviceName -} - -// Reconcile idempotently creates or updates a NAT gateway. -// Only when the NAT gateway 'Name' property is defined we create the NAT gateway: it's opt-in. -func (s *Service) Reconcile(ctx context.Context) error { - ctx, log, done := tele.StartSpanWithLogger(ctx, "natgateways.Service.Reconcile") - defer done() - - ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureServiceReconcileTimeout) - defer cancel() - - if managed, err := s.IsManaged(ctx); err == nil && !managed { - log.V(4).Info("Skipping nat gateways reconcile in custom vnet mode") - return nil - } else if err != nil { - return errors.Wrap(err, "failed to check if NAT gateways are managed") - } - - // We go through the list of NatGatewaySpecs to reconcile each one, independently of the resultingErr of the previous one. - specs := s.Scope.NatGatewaySpecs() - if len(specs) == 0 { - return nil - } - - // If multiple errors occur, we return the most pressing one. - // Order of precedence (highest -> lowest) is: error that is not an operationNotDoneError (ie. error creating) -> operationNotDoneError (ie. creating in progress) -> no error (ie. created) - var resultingErr error - for _, natGatewaySpec := range specs { - result, err := s.CreateOrUpdateResource(ctx, natGatewaySpec, serviceName) - if err != nil { - if !azure.IsOperationNotDoneError(err) || resultingErr == nil { - resultingErr = err - } - } - if err == nil { - natGateway, ok := result.(armnetwork.NatGateway) - if !ok { - // Return out of loop since this would be an unexpected fatal error - resultingErr = errors.Errorf("created resource %T is not an armnetwork.NatGateway", result) - break - } - - // TODO: ideally we wouldn't need to set the subnet spec based on the result of the create operation - s.Scope.SetNatGatewayIDInSubnets(natGatewaySpec.ResourceName(), *natGateway.ID) - } - } - - s.Scope.UpdatePutStatus(infrav1.NATGatewaysReadyCondition, serviceName, resultingErr) - return resultingErr -} - -// Delete deletes the NAT gateway with the provided name. -func (s *Service) Delete(ctx context.Context) error { - ctx, log, done := tele.StartSpanWithLogger(ctx, "natgateways.Service.Delete") - defer done() - - ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureServiceReconcileTimeout) - defer cancel() - - if managed, err := s.IsManaged(ctx); err == nil && !managed { - log.V(4).Info("Skipping nat gateway deletion in custom vnet mode") - return nil - } else if err != nil { - return errors.Wrap(err, "failed to check if NAT gateways are managed") - } - - specs := s.Scope.NatGatewaySpecs() - if len(specs) == 0 { - return nil - } - - // We go through the list of NatGatewaySpecs to delete each one, independently of the resultingErr of the previous one. - // If multiple errors occur, we return the most pressing one. - // Order of precedence (highest -> lowest) is: error that is not an operationNotDoneError (ie. error creating) -> operationNotDoneError (ie. creating in progress) -> no error (ie. created) - var resultingErr error - for _, natGatewaySpec := range specs { - if err := s.DeleteResource(ctx, natGatewaySpec, serviceName); err != nil { - if !azure.IsOperationNotDoneError(err) || resultingErr == nil { - resultingErr = err - } - } + Scope: scope, + Service: svc, } - s.Scope.UpdateDeleteStatus(infrav1.NATGatewaysReadyCondition, serviceName, resultingErr) - return resultingErr -} - -// IsManaged returns true if the NAT gateways' lifecycles are managed. -func (s *Service) IsManaged(ctx context.Context) (bool, error) { - _, _, done := tele.StartSpanWithLogger(ctx, "natgateways.Service.IsManaged") - defer done() - - return s.Scope.IsVnetManaged(), nil } diff --git a/azure/services/natgateways/natgateways_test.go b/azure/services/natgateways/natgateways_test.go deleted file mode 100644 index 3ad663b422a..00000000000 --- a/azure/services/natgateways/natgateways_test.go +++ /dev/null @@ -1,229 +0,0 @@ -/* -Copyright 2020 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 natgateways - -import ( - "context" - "net/http" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" - "github.com/Azure/go-autorest/autorest" - . "github.com/onsi/gomega" - "go.uber.org/mock/gomock" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/utils/ptr" - infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" - "sigs.k8s.io/cluster-api-provider-azure/azure" - "sigs.k8s.io/cluster-api-provider-azure/azure/services/async/mock_async" - "sigs.k8s.io/cluster-api-provider-azure/azure/services/natgateways/mock_natgateways" - gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" -) - -func init() { - _ = clusterv1.AddToScheme(scheme.Scheme) -} - -var ( - natGatewaySpec1 = NatGatewaySpec{ - Name: "my-node-natgateway-1", - ResourceGroup: "my-rg", - SubscriptionID: "my-sub", - Location: "westus", - ClusterName: "my-cluster", - NatGatewayIP: infrav1.PublicIPSpec{Name: "pip-node-subnet"}, - } - natGateway1 = armnetwork.NatGateway{ - ID: ptr.To("/subscriptions/my-sub/resourceGroups/my-rg/providers/Microsoft.Network/natGateways/my-node-natgateway-1"), - } - customVNetTags = infrav1.Tags{ - "Name": "my-vnet", - "sigs.k8s.io_cluster-api-provider-azure_cluster_test-cluster": "shared", - "sigs.k8s.io_cluster-api-provider-azure_role": "common", - } - ownedVNetTags = infrav1.Tags{ - "Name": "my-vnet", - "sigs.k8s.io_cluster-api-provider-azure_cluster_test-cluster": "owned", - "sigs.k8s.io_cluster-api-provider-azure_role": "common", - } - internalError = autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: http.StatusInternalServerError}, "Internal Server Error") -) - -func TestReconcileNatGateways(t *testing.T) { - testcases := []struct { - name string - tags infrav1.Tags - expectedError string - expect func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) - }{ - { - name: "noop if no NAT gateways specs are found", - tags: customVNetTags, - expectedError: "", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{}) - }, - }, - { - name: "NAT gateways in custom vnet mode", - tags: customVNetTags, - expectedError: "", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(false) - }, - }, - { - name: "NAT gateway create successfully", - tags: ownedVNetTags, - expectedError: "", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{&natGatewaySpec1}) - r.CreateOrUpdateResource(gomockinternal.AContext(), &natGatewaySpec1, serviceName).Return(natGateway1, nil) - s.SetNatGatewayIDInSubnets(natGatewaySpec1.Name, *natGateway1.ID) - s.UpdatePutStatus(infrav1.NATGatewaysReadyCondition, serviceName, nil) - }, - }, - { - name: "fail to create a NAT gateway", - tags: ownedVNetTags, - expectedError: "#: Internal Server Error: StatusCode=500", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{&natGatewaySpec1}) - r.CreateOrUpdateResource(gomockinternal.AContext(), &natGatewaySpec1, serviceName).Return(nil, internalError) - s.UpdatePutStatus(infrav1.NATGatewaysReadyCondition, serviceName, internalError) - }, - }, - { - name: "result is not a NAT gateway", - tags: ownedVNetTags, - expectedError: "created resource string is not an armnetwork.NatGateway", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{&natGatewaySpec1}) - r.CreateOrUpdateResource(gomockinternal.AContext(), &natGatewaySpec1, serviceName).Return("not a nat gateway", nil) - s.UpdatePutStatus(infrav1.NATGatewaysReadyCondition, serviceName, gomockinternal.ErrStrEq("created resource string is not an armnetwork.NatGateway")) - }, - }, - } - - for _, tc := range testcases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - g := NewWithT(t) - t.Parallel() - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - scopeMock := mock_natgateways.NewMockNatGatewayScope(mockCtrl) - asyncMock := mock_async.NewMockReconciler(mockCtrl) - - tc.expect(scopeMock.EXPECT(), asyncMock.EXPECT()) - - s := &Service{ - Scope: scopeMock, - Reconciler: asyncMock, - } - - err := s.Reconcile(context.TODO()) - if tc.expectedError != "" { - g.Expect(err).To(HaveOccurred()) - g.Expect(err).To(MatchError(tc.expectedError)) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } - }) - } -} - -func TestDeleteNatGateway(t *testing.T) { - testcases := []struct { - name string - tags infrav1.Tags - expectedError string - expect func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) - }{ - { - name: "noop if no NAT gateways specs are found", - tags: ownedVNetTags, - expectedError: "", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{}) - }, - }, - { - name: "NAT gateways in custom vnet mode", - tags: customVNetTags, - expectedError: "", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(false) - }, - }, - { - name: "NAT gateway deleted successfully", - tags: ownedVNetTags, - expectedError: "", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{&natGatewaySpec1}) - r.DeleteResource(gomockinternal.AContext(), &natGatewaySpec1, serviceName).Return(nil) - s.UpdateDeleteStatus(infrav1.NATGatewaysReadyCondition, serviceName, nil) - }, - }, - { - name: "NAT gateway deletion fails", - tags: ownedVNetTags, - expectedError: "#: Internal Server Error: StatusCode=500", - expect: func(s *mock_natgateways.MockNatGatewayScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { - s.IsVnetManaged().Return(true) - s.NatGatewaySpecs().Return([]azure.ResourceSpecGetter{&natGatewaySpec1}) - r.DeleteResource(gomockinternal.AContext(), &natGatewaySpec1, serviceName).Return(internalError) - s.UpdateDeleteStatus(infrav1.NATGatewaysReadyCondition, serviceName, internalError) - }, - }, - } - - for _, tc := range testcases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - g := NewWithT(t) - t.Parallel() - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - scopeMock := mock_natgateways.NewMockNatGatewayScope(mockCtrl) - asyncMock := mock_async.NewMockReconciler(mockCtrl) - - tc.expect(scopeMock.EXPECT(), asyncMock.EXPECT()) - - s := &Service{ - Scope: scopeMock, - Reconciler: asyncMock, - } - - err := s.Delete(context.TODO()) - if tc.expectedError != "" { - g.Expect(err).To(HaveOccurred()) - g.Expect(err).To(MatchError(tc.expectedError)) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } - }) - } -} diff --git a/azure/services/natgateways/spec.go b/azure/services/natgateways/spec.go index 6f0323126ac..107cbad139a 100644 --- a/azure/services/natgateways/spec.go +++ b/azure/services/natgateways/spec.go @@ -18,19 +18,19 @@ package natgateways import ( "context" - - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" - "github.com/pkg/errors" + asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/azure" - "sigs.k8s.io/cluster-api-provider-azure/azure/converters" azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure" ) // NatGatewaySpec defines the specification for a NAT gateway. type NatGatewaySpec struct { Name string + Namespace string ResourceGroup string SubscriptionID string Location string @@ -39,61 +39,65 @@ type NatGatewaySpec struct { AdditionalTags infrav1.Tags } -// ResourceName returns the name of the NAT gateway. -func (s *NatGatewaySpec) ResourceName() string { - return s.Name -} - -// ResourceGroupName returns the name of the resource group. -func (s *NatGatewaySpec) ResourceGroupName() string { - return s.ResourceGroup -} - -// OwnerResourceName is a no-op for NAT gateways. -func (s *NatGatewaySpec) OwnerResourceName() string { - return "" +// ResourceRef implements azure.ASOResourceSpecGetter. +func (s *NatGatewaySpec) ResourceRef() *asonetworkv1.NatGateway { + return &asonetworkv1.NatGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name, + Namespace: s.Namespace, + }, + } } -// Parameters returns the parameters for the NAT gateway. -func (s *NatGatewaySpec) Parameters(ctx context.Context, existing interface{}) (params interface{}, err error) { +// Parameters returns the parameters for the NAT gateway. It implements azure.ASOResourceSpecGetter. +func (s *NatGatewaySpec) Parameters(ctx context.Context, existing *asonetworkv1.NatGateway) (params *asonetworkv1.NatGateway, err error) { + // no need to set ObjectMeta as it is already set in ResourceRef + natGatewayToCreate := &asonetworkv1.NatGateway{} if existing != nil { - existingNatGateway, ok := existing.(armnetwork.NatGateway) - if !ok { - return nil, errors.Errorf("%T is not an armnetwork.NatGateway", existing) - } - - if hasPublicIP(existingNatGateway, s.NatGatewayIP.Name) { + if hasPublicIP(existing, s.NatGatewayIP.Name) { // Skip update for NAT gateway as it exists with expected values return nil, nil } + // no need for deepcopy as "existing" is already a deep copy + natGatewayToCreate = existing } - natGatewayToCreate := armnetwork.NatGateway{ - Name: ptr.To(s.Name), + natGatewayToCreate.Spec = asonetworkv1.NatGateway_Spec{ Location: ptr.To(s.Location), - SKU: &armnetwork.NatGatewaySKU{Name: ptr.To(armnetwork.NatGatewaySKUNameStandard)}, - Properties: &armnetwork.NatGatewayPropertiesFormat{ - PublicIPAddresses: []*armnetwork.SubResource{ - { - ID: ptr.To(azure.PublicIPID(s.SubscriptionID, s.ResourceGroupName(), s.NatGatewayIP.Name)), + Sku: &asonetworkv1.NatGatewaySku{ + Name: ptr.To(asonetworkv1.NatGatewaySku_Name_Standard), + }, + PublicIpAddresses: []asonetworkv1.ApplicationGatewaySubResource{ + { + Reference: &genruntime.ResourceReference{ + ARMID: azure.PublicIPID(s.SubscriptionID, s.ResourceGroup, s.NatGatewayIP.Name), }, }, }, - Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ + Tags: infrav1.Build(infrav1.BuildParams{ ClusterName: s.ClusterName, Lifecycle: infrav1.ResourceLifecycleOwned, Name: ptr.To(s.Name), Additional: s.AdditionalTags, - })), + }), } return natGatewayToCreate, nil } -func hasPublicIP(natGateway armnetwork.NatGateway, publicIPName string) bool { - for _, publicIP := range natGateway.Properties.PublicIPAddresses { - if publicIP != nil && publicIP.ID != nil { - resource, err := azureutil.ParseResourceID(*publicIP.ID) +// WasManaged implements azure.ASOResourceSpecGetter. +func (s *NatGatewaySpec) WasManaged(resource *asonetworkv1.NatGateway) bool { + // TODO: Earlier implementation of IsManaged(with ASO's framework is morphed into WasManaged) was checking for s.Scope.IsVnetManaged() + // we would want to update this to check for the same once we have the support for VnetManaged in ASO + // we return true for now so that CAPZ can automatically "manage" the nat gateway + return true +} + +// hasPublicIP returns true if the public IP Name is already attached to the NAT gateway. +func hasPublicIP(natGateway *asonetworkv1.NatGateway, publicIPName string) bool { + for _, publicIP := range natGateway.Status.PublicIpAddresses { + if publicIP.Id != nil { + resource, err := azureutil.ParseResourceID(*publicIP.Id) if err != nil { continue } diff --git a/config/aso/crds.yaml b/config/aso/crds.yaml index dc266c89512..de1cc5ba697 100644 --- a/config/aso/crds.yaml +++ b/config/aso/crds.yaml @@ -1,5 +1,522 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: azureserviceoperator-system/azureserviceoperator-serving-cert + controller-gen.kubebuilder.io/version: v0.13.0 + labels: + app.kubernetes.io/name: azure-service-operator + app.kubernetes.io/version: v2.3.0 + name: natgateways.network.azure.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: azureserviceoperator-webhook-service + namespace: azureserviceoperator-system + path: /convert + port: 443 + conversionReviewVersions: + - v1 + group: network.azure.com + names: + kind: NatGateway + listKind: NatGatewayList + plural: natgateways + singular: natgateway + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].severity + name: Severity + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].reason + name: Reason + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Message + type: string + name: v1api20220701 + schema: + openAPIV3Schema: + description: 'Generator information: - Generated from: /network/resource-manager/Microsoft.Network/stable/2022-07-01/natGateway.json - ARM URI: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/natGateways/{natGatewayName}' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + azureName: + description: 'AzureName: The name of the resource in Azure. This is often the same as the name of the resource in Kubernetes but it doesn''t have to be.' + type: string + idleTimeoutInMinutes: + description: 'IdleTimeoutInMinutes: The idle timeout of the nat gateway.' + type: integer + location: + description: 'Location: Resource location.' + type: string + owner: + description: 'Owner: The owner of the resource. The owner controls where the resource goes when it is deployed. The owner also controls the resources lifecycle. When the owner is deleted the resource will also be deleted. Owner is expected to be a reference to a resources.azure.com/ResourceGroup resource' + properties: + armId: + pattern: (?i)(^(/subscriptions/([^/]+)(/resourcegroups/([^/]+))?)?/providers/([^/]+)/([^/]+/[^/]+)(/([^/]+/[^/]+))*$|^/subscriptions/([^/]+)(/resourcegroups/([^/]+))?$) + type: string + name: + description: This is the name of the Kubernetes resource to reference. + type: string + type: object + publicIpAddresses: + description: 'PublicIpAddresses: An array of public ip addresses associated with the nat gateway resource.' + items: + description: Reference to another subresource. + properties: + reference: + description: 'Reference: Resource ID.' + properties: + armId: + description: ARMID is a string of the form /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}. The /resourcegroups/{resourceGroupName} bit is optional as some resources are scoped at the subscription level ARMID is mutually exclusive with Group, Kind, Namespace and Name. + pattern: (?i)(^(/subscriptions/([^/]+)(/resourcegroups/([^/]+))?)?/providers/([^/]+)/([^/]+/[^/]+)(/([^/]+/[^/]+))*$|^/subscriptions/([^/]+)(/resourcegroups/([^/]+))?$) + type: string + group: + description: Group is the Kubernetes group of the resource. + type: string + kind: + description: Kind is the Kubernetes kind of the resource. + type: string + name: + description: Name is the Kubernetes name of the resource. + type: string + type: object + type: object + type: array + publicIpPrefixes: + description: 'PublicIpPrefixes: An array of public ip prefixes associated with the nat gateway resource.' + items: + description: Reference to another subresource. + properties: + reference: + description: 'Reference: Resource ID.' + properties: + armId: + description: ARMID is a string of the form /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}. The /resourcegroups/{resourceGroupName} bit is optional as some resources are scoped at the subscription level ARMID is mutually exclusive with Group, Kind, Namespace and Name. + pattern: (?i)(^(/subscriptions/([^/]+)(/resourcegroups/([^/]+))?)?/providers/([^/]+)/([^/]+/[^/]+)(/([^/]+/[^/]+))*$|^/subscriptions/([^/]+)(/resourcegroups/([^/]+))?$) + type: string + group: + description: Group is the Kubernetes group of the resource. + type: string + kind: + description: Kind is the Kubernetes kind of the resource. + type: string + name: + description: Name is the Kubernetes name of the resource. + type: string + type: object + type: object + type: array + sku: + description: 'Sku: The nat gateway SKU.' + properties: + name: + description: 'Name: Name of Nat Gateway SKU.' + enum: + - Standard + type: string + type: object + tags: + additionalProperties: + type: string + description: 'Tags: Resource tags.' + type: object + zones: + description: 'Zones: A list of availability zones denoting the zone in which Nat Gateway should be deployed.' + items: + type: string + type: array + required: + - owner + type: object + status: + description: Nat Gateway resource. + properties: + conditions: + description: 'Conditions: The observed state of the resource' + items: + description: Condition defines an extension to status (an observation) of a resource + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. + format: date-time + type: string + message: + description: Message is a human readable message indicating details about the transition. This field may be empty. + type: string + observedGeneration: + description: ObservedGeneration is the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: Reason for the condition's last transition. Reasons are upper CamelCase (PascalCase) with no spaces. A reason is always provided, this field will not be empty. + type: string + severity: + description: Severity with which to treat failures of this type of condition. For conditions which have positive polarity (Status == True is their normal/healthy state), this will be omitted when Status == True For conditions which have negative polarity (Status == False is their normal/healthy state), this will be omitted when Status == False. This is omitted in all cases when Status == Unknown + type: string + status: + description: Status of the condition, one of True, False, or Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + etag: + description: 'Etag: A unique read-only string that changes whenever the resource is updated.' + type: string + id: + description: 'Id: Resource ID.' + type: string + idleTimeoutInMinutes: + description: 'IdleTimeoutInMinutes: The idle timeout of the nat gateway.' + type: integer + location: + description: 'Location: Resource location.' + type: string + name: + description: 'Name: Resource name.' + type: string + provisioningState: + description: 'ProvisioningState: The provisioning state of the NAT gateway resource.' + type: string + publicIpAddresses: + description: 'PublicIpAddresses: An array of public ip addresses associated with the nat gateway resource.' + items: + description: Reference to another subresource. + properties: + id: + description: 'Id: Resource ID.' + type: string + type: object + type: array + publicIpPrefixes: + description: 'PublicIpPrefixes: An array of public ip prefixes associated with the nat gateway resource.' + items: + description: Reference to another subresource. + properties: + id: + description: 'Id: Resource ID.' + type: string + type: object + type: array + resourceGuid: + description: 'ResourceGuid: The resource GUID property of the NAT gateway resource.' + type: string + sku: + description: 'Sku: The nat gateway SKU.' + properties: + name: + description: 'Name: Name of Nat Gateway SKU.' + type: string + type: object + subnets: + description: 'Subnets: An array of references to the subnets using this nat gateway resource.' + items: + description: Reference to another subresource. + properties: + id: + description: 'Id: Resource ID.' + type: string + type: object + type: array + tags: + additionalProperties: + type: string + description: 'Tags: Resource tags.' + type: object + type: + description: 'Type: Resource type.' + type: string + zones: + description: 'Zones: A list of availability zones denoting the zone in which Nat Gateway should be deployed.' + items: + type: string + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].severity + name: Severity + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].reason + name: Reason + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Message + type: string + name: v1api20220701storage + schema: + openAPIV3Schema: + description: 'Storage version of v1api20220701.NatGateway Generator information: - Generated from: /network/resource-manager/Microsoft.Network/stable/2022-07-01/natGateway.json - ARM URI: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/natGateways/{natGatewayName}' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Storage version of v1api20220701.NatGateway_Spec + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + azureName: + description: 'AzureName: The name of the resource in Azure. This is often the same as the name of the resource in Kubernetes but it doesn''t have to be.' + type: string + idleTimeoutInMinutes: + type: integer + location: + type: string + originalVersion: + type: string + owner: + description: 'Owner: The owner of the resource. The owner controls where the resource goes when it is deployed. The owner also controls the resources lifecycle. When the owner is deleted the resource will also be deleted. Owner is expected to be a reference to a resources.azure.com/ResourceGroup resource' + properties: + armId: + pattern: (?i)(^(/subscriptions/([^/]+)(/resourcegroups/([^/]+))?)?/providers/([^/]+)/([^/]+/[^/]+)(/([^/]+/[^/]+))*$|^/subscriptions/([^/]+)(/resourcegroups/([^/]+))?$) + type: string + name: + description: This is the name of the Kubernetes resource to reference. + type: string + type: object + publicIpAddresses: + items: + description: Storage version of v1api20220701.ApplicationGatewaySubResource Reference to another subresource. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + reference: + description: 'Reference: Resource ID.' + properties: + armId: + description: ARMID is a string of the form /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}. The /resourcegroups/{resourceGroupName} bit is optional as some resources are scoped at the subscription level ARMID is mutually exclusive with Group, Kind, Namespace and Name. + pattern: (?i)(^(/subscriptions/([^/]+)(/resourcegroups/([^/]+))?)?/providers/([^/]+)/([^/]+/[^/]+)(/([^/]+/[^/]+))*$|^/subscriptions/([^/]+)(/resourcegroups/([^/]+))?$) + type: string + group: + description: Group is the Kubernetes group of the resource. + type: string + kind: + description: Kind is the Kubernetes kind of the resource. + type: string + name: + description: Name is the Kubernetes name of the resource. + type: string + type: object + type: object + type: array + publicIpPrefixes: + items: + description: Storage version of v1api20220701.ApplicationGatewaySubResource Reference to another subresource. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + reference: + description: 'Reference: Resource ID.' + properties: + armId: + description: ARMID is a string of the form /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}. The /resourcegroups/{resourceGroupName} bit is optional as some resources are scoped at the subscription level ARMID is mutually exclusive with Group, Kind, Namespace and Name. + pattern: (?i)(^(/subscriptions/([^/]+)(/resourcegroups/([^/]+))?)?/providers/([^/]+)/([^/]+/[^/]+)(/([^/]+/[^/]+))*$|^/subscriptions/([^/]+)(/resourcegroups/([^/]+))?$) + type: string + group: + description: Group is the Kubernetes group of the resource. + type: string + kind: + description: Kind is the Kubernetes kind of the resource. + type: string + name: + description: Name is the Kubernetes name of the resource. + type: string + type: object + type: object + type: array + sku: + description: Storage version of v1api20220701.NatGatewaySku SKU of nat gateway. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + name: + type: string + type: object + tags: + additionalProperties: + type: string + type: object + zones: + items: + type: string + type: array + required: + - owner + type: object + status: + description: Storage version of v1api20220701.NatGateway_STATUS Nat Gateway resource. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + conditions: + items: + description: Condition defines an extension to status (an observation) of a resource + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. + format: date-time + type: string + message: + description: Message is a human readable message indicating details about the transition. This field may be empty. + type: string + observedGeneration: + description: ObservedGeneration is the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: Reason for the condition's last transition. Reasons are upper CamelCase (PascalCase) with no spaces. A reason is always provided, this field will not be empty. + type: string + severity: + description: Severity with which to treat failures of this type of condition. For conditions which have positive polarity (Status == True is their normal/healthy state), this will be omitted when Status == True For conditions which have negative polarity (Status == False is their normal/healthy state), this will be omitted when Status == False. This is omitted in all cases when Status == Unknown + type: string + status: + description: Status of the condition, one of True, False, or Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + etag: + type: string + id: + type: string + idleTimeoutInMinutes: + type: integer + location: + type: string + name: + type: string + provisioningState: + type: string + publicIpAddresses: + items: + description: Storage version of v1api20220701.ApplicationGatewaySubResource_STATUS Reference to another subresource. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + id: + type: string + type: object + type: array + publicIpPrefixes: + items: + description: Storage version of v1api20220701.ApplicationGatewaySubResource_STATUS Reference to another subresource. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + id: + type: string + type: object + type: array + resourceGuid: + type: string + sku: + description: Storage version of v1api20220701.NatGatewaySku_STATUS SKU of nat gateway. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + name: + type: string + type: object + subnets: + items: + description: Storage version of v1api20220701.ApplicationGatewaySubResource_STATUS Reference to another subresource. + properties: + $propertyBag: + additionalProperties: + type: string + description: PropertyBag is an unordered set of stashed information that used for properties not directly supported by storage resources, allowing for full fidelity round trip conversions + type: object + id: + type: string + type: object + type: array + tags: + additionalProperties: + type: string + type: object + type: + type: string + zones: + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: azureserviceoperator-system/azureserviceoperator-serving-cert diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ce557be6b40..3cf9633c4c2 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -261,6 +261,26 @@ rules: - get - patch - update +- apiGroups: + - network.azure.com + resources: + - natgateways + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - network.azure.com + resources: + - natgateways/status + verbs: + - get + - list + - watch - apiGroups: - resources.azure.com resources: diff --git a/controllers/azurecluster_controller.go b/controllers/azurecluster_controller.go index eb20215cb01..a4c894a6127 100644 --- a/controllers/azurecluster_controller.go +++ b/controllers/azurecluster_controller.go @@ -113,6 +113,8 @@ func (acr *AzureClusterReconciler) SetupWithManager(ctx context.Context, mgr ctr // +kubebuilder:rbac:groups="",resources=namespaces,verbs=list; // +kubebuilder:rbac:groups=resources.azure.com,resources=resourcegroups,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=resources.azure.com,resources=resourcegroups/status,verbs=get;list;watch +// +kubebuilder:rbac:groups=network.azure.com,resources=natgateways,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=network.azure.com,resources=natgateways/status,verbs=get;list;watch // Reconcile idempotently gets, creates, and updates a cluster. func (acr *AzureClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { diff --git a/controllers/azurecluster_reconciler.go b/controllers/azurecluster_reconciler.go index 551cb7a0b88..8d45b62765f 100644 --- a/controllers/azurecluster_reconciler.go +++ b/controllers/azurecluster_reconciler.go @@ -70,10 +70,6 @@ func newAzureClusterService(scope *scope.ClusterScope) (*azureClusterService, er if err != nil { return nil, err } - natGatewaysSvc, err := natgateways.New(scope) - if err != nil { - return nil, err - } publicIPsSvc, err := publicips.New(scope) if err != nil { return nil, err @@ -106,7 +102,7 @@ func newAzureClusterService(scope *scope.ClusterScope) (*azureClusterService, er securityGroupsSvc, routeTablesSvc, publicIPsSvc, - natGatewaysSvc, + natgateways.New(scope), subnetsSvc, vnetPeeringsSvc, loadbalancersSvc, diff --git a/main.go b/main.go index b81da918138..3f79e50bad6 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( // +kubebuilder:scaffold:imports aadpodv1 "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity/v1" + asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701" asoresourcesv1 "github.com/Azure/azure-service-operator/v2/api/resources/v1api20200601" "github.com/spf13/pflag" corev1 "k8s.io/api/core/v1" @@ -74,6 +75,7 @@ func init() { _ = expv1.AddToScheme(scheme) _ = kubeadmv1.AddToScheme(scheme) _ = asoresourcesv1.AddToScheme(scheme) + _ = asonetworkv1.AddToScheme(scheme) // +kubebuilder:scaffold:scheme // Add aadpodidentity v1 to the scheme.