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  }