sigs.k8s.io/cluster-api-provider-azure@v1.17.0/controllers/azurejson_machinepool_controller.go (about)

     1  /*
     2  Copyright 2020 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 controllers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/pkg/errors"
    24  	corev1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/client-go/tools/record"
    28  	"k8s.io/utils/ptr"
    29  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    30  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/identities"
    31  	infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1"
    32  	azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
    33  	"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    34  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    35  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    36  	"sigs.k8s.io/cluster-api/util"
    37  	"sigs.k8s.io/cluster-api/util/predicates"
    38  	ctrl "sigs.k8s.io/controller-runtime"
    39  	"sigs.k8s.io/controller-runtime/pkg/builder"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  	"sigs.k8s.io/controller-runtime/pkg/controller"
    42  	"sigs.k8s.io/controller-runtime/pkg/handler"
    43  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    44  )
    45  
    46  // AzureJSONMachinePoolReconciler reconciles Azure json secrets for AzureMachinePool objects.
    47  type AzureJSONMachinePoolReconciler struct {
    48  	client.Client
    49  	Recorder         record.EventRecorder
    50  	Timeouts         reconciler.Timeouts
    51  	WatchFilterValue string
    52  }
    53  
    54  // SetupWithManager initializes this controller with a manager.
    55  func (r *AzureJSONMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
    56  	_, log, done := tele.StartSpanWithLogger(ctx,
    57  		"controllers.AzureJSONMachinePoolReconciler.SetupWithManager",
    58  	)
    59  	defer done()
    60  
    61  	azureMachinePoolMapper, err := util.ClusterToTypedObjectsMapper(r.Client, &infrav1exp.AzureMachinePoolList{}, mgr.GetScheme())
    62  	if err != nil {
    63  		return errors.Wrap(err, "failed to create mapper for Cluster to AzureMachinePools")
    64  	}
    65  
    66  	return ctrl.NewControllerManagedBy(mgr).
    67  		WithOptions(options).
    68  		For(&infrav1exp.AzureMachinePool{}).
    69  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue)).
    70  		Owns(&corev1.Secret{}).
    71  		// Add a watch on Clusters to requeue when the infraRef is set. This is needed because the infraRef is not initially
    72  		// set in Clusters created from a ClusterClass.
    73  		Watches(
    74  			&clusterv1.Cluster{},
    75  			handler.EnqueueRequestsFromMapFunc(azureMachinePoolMapper),
    76  			builder.WithPredicates(
    77  				predicates.ClusterUnpausedAndInfrastructureReady(log),
    78  				predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue),
    79  			),
    80  		).
    81  		Complete(r)
    82  }
    83  
    84  // Reconcile reconciles the Azure json for AzureMachinePool objects.
    85  func (r *AzureJSONMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
    86  	ctx, cancel := context.WithTimeout(ctx, r.Timeouts.DefaultedLoopTimeout())
    87  	defer cancel()
    88  
    89  	ctx, log, done := tele.StartSpanWithLogger(
    90  		ctx,
    91  		"controllers.AzureJSONMachinePoolReconciler.Reconcile",
    92  		tele.KVP("namespace", req.Namespace),
    93  		tele.KVP("name", req.Name),
    94  		tele.KVP("kind", infrav1.AzureMachinePoolKind),
    95  	)
    96  	defer done()
    97  
    98  	log = log.WithValues("namespace", req.Namespace, "azureMachinePool", req.Name)
    99  
   100  	// Fetch the AzureMachine instance
   101  	azureMachinePool := &infrav1exp.AzureMachinePool{}
   102  	err := r.Get(ctx, req.NamespacedName, azureMachinePool)
   103  	if err != nil {
   104  		if apierrors.IsNotFound(err) {
   105  			log.Info("object was not found")
   106  			return reconcile.Result{}, nil
   107  		}
   108  		return reconcile.Result{}, err
   109  	}
   110  
   111  	// Fetch the CAPI MachinePool.
   112  	machinePool, err := GetOwnerMachinePool(ctx, r.Client, azureMachinePool.ObjectMeta)
   113  	if err != nil {
   114  		return reconcile.Result{}, err
   115  	}
   116  	if machinePool == nil {
   117  		log.Info("MachinePool Controller has not yet set OwnerRef")
   118  		return reconcile.Result{}, nil
   119  	}
   120  
   121  	log = log.WithValues("machinePool", machinePool.Name)
   122  
   123  	// Fetch the Cluster.
   124  	cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machinePool.ObjectMeta)
   125  	if err != nil {
   126  		log.Info("MachinePool is missing cluster label or cluster does not exist")
   127  		return reconcile.Result{}, nil
   128  	}
   129  
   130  	log = log.WithValues("cluster", cluster.Name)
   131  
   132  	if cluster.Spec.InfrastructureRef == nil {
   133  		log.Info("infra ref is nil")
   134  		return ctrl.Result{}, nil
   135  	}
   136  
   137  	clusterScope, err := GetClusterScoper(ctx, log, r.Client, cluster, r.Timeouts)
   138  	if err != nil {
   139  		return reconcile.Result{}, errors.Wrapf(err, "failed to create cluster scope for cluster %s/%s", cluster.Namespace, cluster.Name)
   140  	}
   141  
   142  	// Construct secret for this machine
   143  	userAssignedIdentityIfExists := ""
   144  	if len(azureMachinePool.Spec.UserAssignedIdentities) > 0 {
   145  		var identitiesClient identities.Client
   146  		identitiesClient, err := getClient(clusterScope)
   147  		if err != nil {
   148  			return reconcile.Result{}, errors.Wrap(err, "failed to create identities client")
   149  		}
   150  		parsed, err := azureutil.ParseResourceID(azureMachinePool.Spec.UserAssignedIdentities[0].ProviderID)
   151  		if err != nil {
   152  			return reconcile.Result{}, errors.Wrapf(err, "failed to parse ProviderID %s", azureMachinePool.Spec.UserAssignedIdentities[0].ProviderID)
   153  		}
   154  		if parsed.SubscriptionID != clusterScope.SubscriptionID() {
   155  			identitiesClient, err = identities.NewClientBySub(clusterScope, parsed.SubscriptionID)
   156  			if err != nil {
   157  				return reconcile.Result{}, errors.Wrapf(err, "failed to create identities client from subscription ID %s", parsed.SubscriptionID)
   158  			}
   159  		}
   160  		userAssignedIdentityIfExists, err = identitiesClient.GetClientID(
   161  			ctx, azureMachinePool.Spec.UserAssignedIdentities[0].ProviderID)
   162  		if err != nil {
   163  			return reconcile.Result{}, errors.Wrap(err, "failed to get user-assigned identity ClientID")
   164  		}
   165  	}
   166  
   167  	apiVersion, kind := infrav1.GroupVersion.WithKind(infrav1.AzureMachinePoolKind).ToAPIVersionAndKind()
   168  	owner := metav1.OwnerReference{
   169  		APIVersion: apiVersion,
   170  		Kind:       kind,
   171  		Name:       azureMachinePool.GetName(),
   172  		UID:        azureMachinePool.GetUID(),
   173  		Controller: ptr.To(true),
   174  	}
   175  
   176  	if azureMachinePool.Spec.Identity == infrav1.VMIdentityNone {
   177  		log.Info(fmt.Sprintf("WARNING, %s", spIdentityWarning))
   178  		r.Recorder.Eventf(azureMachinePool, corev1.EventTypeWarning, "VMIdentityNone", spIdentityWarning)
   179  	}
   180  
   181  	newSecret, err := GetCloudProviderSecret(
   182  		clusterScope,
   183  		azureMachinePool.Namespace,
   184  		azureMachinePool.Name,
   185  		owner,
   186  		azureMachinePool.Spec.Identity,
   187  		userAssignedIdentityIfExists,
   188  	)
   189  
   190  	if err != nil {
   191  		return ctrl.Result{}, errors.Wrap(err, "failed to create cloud provider config")
   192  	}
   193  
   194  	if err := reconcileAzureSecret(ctx, r.Client, owner, newSecret, clusterScope.ClusterName()); err != nil {
   195  		r.Recorder.Eventf(azureMachinePool, corev1.EventTypeWarning, "Error reconciling cloud provider secret for AzureMachinePool", err.Error())
   196  		return ctrl.Result{}, errors.Wrap(err, "failed to reconcile azure secret")
   197  	}
   198  
   199  	return ctrl.Result{}, nil
   200  }
   201  
   202  var getClient = identities.NewClient