sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/publicips/publicips.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package publicips 18 19 import ( 20 "context" 21 22 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" 23 "github.com/pkg/errors" 24 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 25 "sigs.k8s.io/cluster-api-provider-azure/azure" 26 "sigs.k8s.io/cluster-api-provider-azure/azure/converters" 27 "sigs.k8s.io/cluster-api-provider-azure/azure/services/async" 28 "sigs.k8s.io/cluster-api-provider-azure/azure/services/tags" 29 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 30 ) 31 32 const serviceName = "publicips" 33 34 // PublicIPScope defines the scope interface for a public IP service. 35 type PublicIPScope interface { 36 azure.Authorizer 37 azure.AsyncStatusUpdater 38 azure.ClusterDescriber 39 PublicIPSpecs() []azure.ResourceSpecGetter 40 } 41 42 // Service provides operations on Azure resources. 43 type Service struct { 44 Scope PublicIPScope 45 async.Reconciler 46 async.Getter 47 async.TagsGetter 48 } 49 50 // New creates a new service. 51 func New(scope PublicIPScope) (*Service, error) { 52 client, err := NewClient(scope, scope.DefaultedAzureCallTimeout()) 53 if err != nil { 54 return nil, err 55 } 56 tagsClient, err := tags.NewClient(scope) 57 if err != nil { 58 return nil, err 59 } 60 return &Service{ 61 Scope: scope, 62 Getter: client, 63 TagsGetter: tagsClient, 64 Reconciler: async.New[armnetwork.PublicIPAddressesClientCreateOrUpdateResponse, armnetwork.PublicIPAddressesClientDeleteResponse](scope, client, client), 65 }, nil 66 } 67 68 // Name returns the service name. 69 func (s *Service) Name() string { 70 return serviceName 71 } 72 73 // Reconcile idempotently creates or updates a public IP. 74 func (s *Service) Reconcile(ctx context.Context) error { 75 ctx, _, done := tele.StartSpanWithLogger(ctx, "publicips.Service.Reconcile") 76 defer done() 77 78 ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout()) 79 defer cancel() 80 81 specs := s.Scope.PublicIPSpecs() 82 if len(specs) == 0 { 83 return nil 84 } 85 86 // We go through the list of PublicIPSpecs to reconcile each one, independently of the result of the previous one. 87 // If multiple errors occur, we return the most pressing one. 88 // Order of precedence (highest -> lowest) is: error that is not an operationNotDoneError (i.e. error creating) -> operationNotDoneError (i.e. creating in progress) -> no error (i.e. created) 89 var result error 90 for _, publicIPSpec := range specs { 91 if _, err := s.CreateOrUpdateResource(ctx, publicIPSpec, serviceName); err != nil { 92 if !azure.IsOperationNotDoneError(err) || result == nil { 93 result = err 94 } 95 } 96 } 97 98 s.Scope.UpdatePutStatus(infrav1.PublicIPsReadyCondition, serviceName, result) 99 return result 100 } 101 102 // Delete deletes the public IP with the provided scope. 103 func (s *Service) Delete(ctx context.Context) error { 104 ctx, log, done := tele.StartSpanWithLogger(ctx, "publicips.Service.Delete") 105 defer done() 106 107 ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout()) 108 defer cancel() 109 110 specs := s.Scope.PublicIPSpecs() 111 if len(specs) == 0 { 112 return nil 113 } 114 115 hasManagedPublicIPs := false 116 117 // We go through the list of VnetPeeringSpecs to delete each one, independently of the result of the previous one. 118 // If multiple errors occur, we return the most pressing one. 119 // Order of precedence (highest -> lowest) is: error that is not an operationNotDoneError (i.e. error deleting) -> operationNotDoneError (i.e. deleting in progress) -> no error (i.e. deleted) 120 var result error 121 for _, publicIPSpec := range specs { 122 managed, err := s.isIPManaged(ctx, publicIPSpec) 123 if err != nil && !azure.ResourceNotFound(err) { 124 return errors.Wrap(err, "could not get public IP management state") 125 } 126 127 if !managed { 128 log.V(2).Info("Skipping IP deletion for unmanaged public IP", "public ip", publicIPSpec.ResourceName()) 129 continue 130 } 131 132 log.V(2).Info("deleting public IP", "public ip", publicIPSpec.ResourceName()) 133 hasManagedPublicIPs = true 134 if err := s.DeleteResource(ctx, publicIPSpec, serviceName); err != nil { 135 if !azure.IsOperationNotDoneError(err) || result == nil { 136 result = err 137 } 138 } 139 140 log.V(2).Info("deleted public IP", "public ip", publicIPSpec.ResourceName()) 141 } 142 143 if hasManagedPublicIPs { 144 s.Scope.UpdateDeleteStatus(infrav1.PublicIPsReadyCondition, serviceName, result) 145 } 146 147 return result 148 } 149 150 // isIPManaged returns true if the IP has an owned tag with the cluster name as value, 151 // meaning that the IP's lifecycle is managed. 152 func (s *Service) isIPManaged(ctx context.Context, spec azure.ResourceSpecGetter) (bool, error) { 153 scope := azure.PublicIPID(s.Scope.SubscriptionID(), spec.ResourceGroupName(), spec.ResourceName()) 154 result, err := s.TagsGetter.GetAtScope(ctx, scope) 155 if err != nil { 156 return false, err 157 } 158 159 tagsMap := make(map[string]*string) 160 if result.Properties != nil && result.Properties.Tags != nil { 161 tagsMap = result.Properties.Tags 162 } 163 164 tags := converters.MapToTags(tagsMap) 165 return tags.HasOwned(s.Scope.ClusterName()), nil 166 } 167 168 // IsManaged returns always returns true as public IPs are managed on a one-by-one basis. 169 func (s *Service) IsManaged(ctx context.Context) (bool, error) { 170 return true, nil 171 }