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

     1  /*
     2  Copyright 2021 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 vmextensions
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    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/services/async"
    27  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    28  )
    29  
    30  const serviceName = "vmextensions"
    31  
    32  // VMExtensionScope defines the scope interface for a vm extension service.
    33  type VMExtensionScope interface {
    34  	azure.Authorizer
    35  	azure.AsyncStatusUpdater
    36  	VMExtensionSpecs() []azure.ResourceSpecGetter
    37  }
    38  
    39  // Service provides operations on Azure resources.
    40  type Service struct {
    41  	Scope VMExtensionScope
    42  	async.Reconciler
    43  }
    44  
    45  // New creates a new vm extension service.
    46  func New(scope VMExtensionScope) (*Service, error) {
    47  	client, err := newClient(scope, scope.DefaultedAzureCallTimeout())
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	return &Service{
    52  		Scope: scope,
    53  		Reconciler: async.New[armcompute.VirtualMachineExtensionsClientCreateOrUpdateResponse,
    54  			armcompute.VirtualMachineExtensionsClientDeleteResponse](scope, client, client),
    55  	}, nil
    56  }
    57  
    58  // Name returns the service name.
    59  func (s *Service) Name() string {
    60  	return serviceName
    61  }
    62  
    63  // Reconcile idempotently creates or updates a VM extension.
    64  func (s *Service) Reconcile(ctx context.Context) error {
    65  	ctx, _, done := tele.StartSpanWithLogger(ctx, "vmextensions.Service.Reconcile")
    66  	defer done()
    67  
    68  	ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout())
    69  	defer cancel()
    70  
    71  	specs := s.Scope.VMExtensionSpecs()
    72  	if len(specs) == 0 {
    73  		return nil
    74  	}
    75  
    76  	// We go through the list of ExtensionSpecs to reconcile each one, independently of the result of the previous one.
    77  	// If multiple errors occur, we return the most pressing one.
    78  	//  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)
    79  	var resultErr error
    80  	for _, extensionSpec := range specs {
    81  		_, err := s.CreateOrUpdateResource(ctx, extensionSpec, serviceName)
    82  		if err != nil {
    83  			if !azure.IsOperationNotDoneError(err) || resultErr == nil {
    84  				resultErr = err
    85  			}
    86  		}
    87  	}
    88  
    89  	if azure.IsOperationNotDoneError(resultErr) {
    90  		resultErr = errors.Wrapf(resultErr, "extension is still in provisioning state. This likely means that bootstrapping has not yet completed on the VM")
    91  	} else if resultErr != nil {
    92  		resultErr = errors.Wrapf(resultErr, "extension state failed. This likely means the Kubernetes node bootstrapping process failed or timed out. Check VM boot diagnostics logs to learn more")
    93  	}
    94  
    95  	s.Scope.UpdatePutStatus(infrav1.BootstrapSucceededCondition, serviceName, resultErr)
    96  	return resultErr
    97  }
    98  
    99  // Delete is a no-op. VM Extensions will be deleted as part of VM deletion.
   100  func (s *Service) Delete(_ context.Context) error {
   101  	return nil
   102  }
   103  
   104  // IsManaged returns always returns true as CAPZ does not support BYO VM extension.
   105  func (s *Service) IsManaged(ctx context.Context) (bool, error) {
   106  	return true, nil
   107  }