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 }