sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/aso/service.go (about)

     1  /*
     2  Copyright 2023 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 aso
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
    23  	"github.com/pkg/errors"
    24  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    25  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    26  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  )
    29  
    30  // Service provides operations on Azure resources.
    31  type Service[T genruntime.MetaObject, S Scope] struct {
    32  	Reconciler[T]
    33  
    34  	Scope S
    35  	Specs []azure.ASOResourceSpecGetter[T]
    36  	// ListFunc is used to enumerate ASO existent resources. Currently this interface is designed only to aid
    37  	// discovery of ASO resources that no longer have a CAPZ reference, and can thus be deleted. This behavior
    38  	// may be skipped for a service by leaving this field nil.
    39  	ListFunc func(ctx context.Context, client client.Client, opts ...client.ListOption) (resources []T, err error)
    40  
    41  	ConditionType                  clusterv1.ConditionType
    42  	PostCreateOrUpdateResourceHook func(ctx context.Context, scope S, result T, err error) error
    43  	PostReconcileHook              func(ctx context.Context, scope S, err error) error
    44  	PostDeleteHook                 func(ctx context.Context, scope S, err error) error
    45  
    46  	name string
    47  }
    48  
    49  // NewService creates a new Service.
    50  func NewService[T genruntime.MetaObject, S Scope](name string, scope S) *Service[T, S] {
    51  	return &Service[T, S]{
    52  		Reconciler: New[T](scope.GetClient(), scope.ClusterName(), scope.ASOOwner()),
    53  		Scope:      scope,
    54  		name:       name,
    55  	}
    56  }
    57  
    58  // Name returns the service name.
    59  func (s *Service[T, S]) Name() string {
    60  	return s.name
    61  }
    62  
    63  // Reconcile idempotently creates or updates the resources.
    64  func (s *Service[T, S]) Reconcile(ctx context.Context) error {
    65  	ctx, _, done := tele.StartSpanWithLogger(ctx, "aso.Service.Reconcile")
    66  	defer done()
    67  
    68  	ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout())
    69  	defer cancel()
    70  
    71  	// We go through the list of Specs to reconcile each one, independently of the result of the previous one.
    72  	// If multiple errors occur, we return the most pressing one.
    73  	// Order of precedence (highest -> lowest) is:
    74  	//   - error that is not an operationNotDoneError (i.e. error creating)
    75  	//   - operationNotDoneError (i.e. creating in progress)
    76  	//   - no error (i.e. created)
    77  	var resultErr error
    78  
    79  	if s.ListFunc != nil {
    80  		toReconcile := map[string]struct{}{}
    81  		for _, spec := range s.Specs {
    82  			toReconcile[spec.ResourceRef().GetName()] = struct{}{}
    83  		}
    84  		list, err := s.ListFunc(ctx, s.Scope.GetClient(), client.InNamespace(s.Scope.ASOOwner().GetNamespace()))
    85  		if err != nil {
    86  			resultErr = err
    87  		} else {
    88  			for _, existing := range list {
    89  				if _, exists := toReconcile[existing.GetName()]; exists {
    90  					continue
    91  				}
    92  				err := s.Reconciler.DeleteResource(ctx, existing, s.Name())
    93  				if err != nil && (!azure.IsOperationNotDoneError(err) || resultErr == nil) {
    94  					resultErr = err
    95  				}
    96  			}
    97  		}
    98  	}
    99  
   100  	for _, spec := range s.Specs {
   101  		result, err := s.CreateOrUpdateResource(ctx, spec, s.Name())
   102  		if s.PostCreateOrUpdateResourceHook != nil {
   103  			err = s.PostCreateOrUpdateResourceHook(ctx, s.Scope, result, err)
   104  		}
   105  		if err != nil && (!azure.IsOperationNotDoneError(err) || resultErr == nil) {
   106  			resultErr = err
   107  		}
   108  	}
   109  
   110  	if s.PostReconcileHook != nil {
   111  		resultErr = s.PostReconcileHook(ctx, s.Scope, resultErr)
   112  	}
   113  	s.Scope.UpdatePutStatus(s.ConditionType, s.Name(), resultErr)
   114  	return resultErr
   115  }
   116  
   117  // Delete deletes the resources.
   118  func (s *Service[T, S]) Delete(ctx context.Context) error {
   119  	ctx, _, done := tele.StartSpanWithLogger(ctx, "aso.Service.Delete")
   120  	defer done()
   121  
   122  	ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout())
   123  	defer cancel()
   124  
   125  	if len(s.Specs) == 0 {
   126  		return nil
   127  	}
   128  
   129  	// We go through the list of Specs to delete each one, independently of the resultErr of the previous one.
   130  	// If multiple errors occur, we return the most pressing one.
   131  	// Order of precedence (highest -> lowest) is:
   132  	//   - error that is not an operationNotDoneError (i.e. error deleting)
   133  	//   - operationNotDoneError (i.e. deleting in progress)
   134  	//   - no error (i.e. deleted)
   135  	var resultErr error
   136  	for _, spec := range s.Specs {
   137  		err := s.DeleteResource(ctx, spec.ResourceRef(), s.Name())
   138  		if err != nil && (!azure.IsOperationNotDoneError(err) || resultErr == nil) {
   139  			resultErr = err
   140  		}
   141  	}
   142  
   143  	if s.PostDeleteHook != nil {
   144  		resultErr = s.PostDeleteHook(ctx, s.Scope, resultErr)
   145  	}
   146  	s.Scope.UpdateDeleteStatus(s.ConditionType, s.Name(), resultErr)
   147  	return resultErr
   148  }
   149  
   150  // Pause implements azure.Pauser.
   151  func (s *Service[T, S]) Pause(ctx context.Context) error {
   152  	var _ azure.Pauser = (*Service[T, S])(nil)
   153  
   154  	ctx, _, done := tele.StartSpanWithLogger(ctx, "aso.Service.Pause")
   155  	defer done()
   156  
   157  	for _, spec := range s.Specs {
   158  		ref := spec.ResourceRef()
   159  		if err := s.PauseResource(ctx, ref, s.Name()); err != nil {
   160  			return errors.Wrapf(err, "failed to pause ASO resource %s %s/%s", ref.GetObjectKind().GroupVersionKind(), s.Scope.ASOOwner().GetNamespace(), ref.GetName())
   161  		}
   162  	}
   163  
   164  	return nil
   165  }