sigs.k8s.io/cluster-api-provider-azure@v1.14.3/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/client"
    40  	"sigs.k8s.io/controller-runtime/pkg/controller"
    41  	"sigs.k8s.io/controller-runtime/pkg/handler"
    42  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    43  	"sigs.k8s.io/controller-runtime/pkg/source"
    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  	c, err := ctrl.NewControllerManagedBy(mgr).
    67  		WithOptions(options).
    68  		For(&infrav1exp.AzureMachinePool{}).
    69  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue)).
    70  		Owns(&corev1.Secret{}).
    71  		Build(r)
    72  
    73  	if err != nil {
    74  		return errors.Wrap(err, "failed to create controller")
    75  	}
    76  
    77  	// Add a watch on Clusters to requeue when the infraRef is set. This is needed because the infraRef is not initially
    78  	// set in Clusters created from a ClusterClass.
    79  	if err := c.Watch(
    80  		source.Kind(mgr.GetCache(), &clusterv1.Cluster{}),
    81  		handler.EnqueueRequestsFromMapFunc(azureMachinePoolMapper),
    82  		predicates.ClusterUnpausedAndInfrastructureReady(log),
    83  		predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue),
    84  	); err != nil {
    85  		return errors.Wrap(err, "failed adding a watch for Clusters")
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  // Reconcile reconciles the Azure json for AzureMachinePool objects.
    92  func (r *AzureJSONMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
    93  	ctx, cancel := context.WithTimeout(ctx, r.Timeouts.DefaultedLoopTimeout())
    94  	defer cancel()
    95  
    96  	ctx, log, done := tele.StartSpanWithLogger(
    97  		ctx,
    98  		"controllers.AzureJSONMachinePoolReconciler.Reconcile",
    99  		tele.KVP("namespace", req.Namespace),
   100  		tele.KVP("name", req.Name),
   101  		tele.KVP("kind", infrav1.AzureMachinePoolKind),
   102  	)
   103  	defer done()
   104  
   105  	log = log.WithValues("namespace", req.Namespace, "azureMachinePool", req.Name)
   106  
   107  	// Fetch the AzureMachine instance
   108  	azureMachinePool := &infrav1exp.AzureMachinePool{}
   109  	err := r.Get(ctx, req.NamespacedName, azureMachinePool)
   110  	if err != nil {
   111  		if apierrors.IsNotFound(err) {
   112  			log.Info("object was not found")
   113  			return reconcile.Result{}, nil
   114  		}
   115  		return reconcile.Result{}, err
   116  	}
   117  
   118  	// Fetch the CAPI MachinePool.
   119  	machinePool, err := GetOwnerMachinePool(ctx, r.Client, azureMachinePool.ObjectMeta)
   120  	if err != nil {
   121  		return reconcile.Result{}, err
   122  	}
   123  	if machinePool == nil {
   124  		log.Info("MachinePool Controller has not yet set OwnerRef")
   125  		return reconcile.Result{}, nil
   126  	}
   127  
   128  	log = log.WithValues("machinePool", machinePool.Name)
   129  
   130  	// Fetch the Cluster.
   131  	cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machinePool.ObjectMeta)
   132  	if err != nil {
   133  		log.Info("MachinePool is missing cluster label or cluster does not exist")
   134  		return reconcile.Result{}, nil
   135  	}
   136  
   137  	log = log.WithValues("cluster", cluster.Name)
   138  
   139  	if cluster.Spec.InfrastructureRef == nil {
   140  		log.Info("infra ref is nil")
   141  		return ctrl.Result{}, nil
   142  	}
   143  
   144  	clusterScope, err := GetClusterScoper(ctx, log, r.Client, cluster, r.Timeouts)
   145  	if err != nil {
   146  		return reconcile.Result{}, errors.Wrapf(err, "failed to create cluster scope for cluster %s/%s", cluster.Namespace, cluster.Name)
   147  	}
   148  
   149  	// Construct secret for this machine
   150  	userAssignedIdentityIfExists := ""
   151  	if len(azureMachinePool.Spec.UserAssignedIdentities) > 0 {
   152  		var identitiesClient identities.Client
   153  		identitiesClient, err := getClient(clusterScope)
   154  		if err != nil {
   155  			return reconcile.Result{}, errors.Wrap(err, "failed to create identities client")
   156  		}
   157  		parsed, err := azureutil.ParseResourceID(azureMachinePool.Spec.UserAssignedIdentities[0].ProviderID)
   158  		if err != nil {
   159  			return reconcile.Result{}, errors.Wrapf(err, "failed to parse ProviderID %s", azureMachinePool.Spec.UserAssignedIdentities[0].ProviderID)
   160  		}
   161  		if parsed.SubscriptionID != clusterScope.SubscriptionID() {
   162  			identitiesClient, err = identities.NewClientBySub(clusterScope, parsed.SubscriptionID)
   163  			if err != nil {
   164  				return reconcile.Result{}, errors.Wrapf(err, "failed to create identities client from subscription ID %s", parsed.SubscriptionID)
   165  			}
   166  		}
   167  		userAssignedIdentityIfExists, err = identitiesClient.GetClientID(
   168  			ctx, azureMachinePool.Spec.UserAssignedIdentities[0].ProviderID)
   169  		if err != nil {
   170  			return reconcile.Result{}, errors.Wrap(err, "failed to get user-assigned identity ClientID")
   171  		}
   172  	}
   173  
   174  	apiVersion, kind := infrav1.GroupVersion.WithKind(infrav1.AzureMachinePoolKind).ToAPIVersionAndKind()
   175  	owner := metav1.OwnerReference{
   176  		APIVersion: apiVersion,
   177  		Kind:       kind,
   178  		Name:       azureMachinePool.GetName(),
   179  		UID:        azureMachinePool.GetUID(),
   180  		Controller: ptr.To(true),
   181  	}
   182  
   183  	if azureMachinePool.Spec.Identity == infrav1.VMIdentityNone {
   184  		log.Info(fmt.Sprintf("WARNING, %s", spIdentityWarning))
   185  		r.Recorder.Eventf(azureMachinePool, corev1.EventTypeWarning, "VMIdentityNone", spIdentityWarning)
   186  	}
   187  
   188  	newSecret, err := GetCloudProviderSecret(
   189  		clusterScope,
   190  		azureMachinePool.Namespace,
   191  		azureMachinePool.Name,
   192  		owner,
   193  		azureMachinePool.Spec.Identity,
   194  		userAssignedIdentityIfExists,
   195  	)
   196  
   197  	if err != nil {
   198  		return ctrl.Result{}, errors.Wrap(err, "failed to create cloud provider config")
   199  	}
   200  
   201  	if err := reconcileAzureSecret(ctx, r.Client, owner, newSecret, clusterScope.ClusterName()); err != nil {
   202  		r.Recorder.Eventf(azureMachinePool, corev1.EventTypeWarning, "Error reconciling cloud provider secret for AzureMachinePool", err.Error())
   203  		return ctrl.Result{}, errors.Wrap(err, "failed to reconcile azure secret")
   204  	}
   205  
   206  	return ctrl.Result{}, nil
   207  }
   208  
   209  var getClient = identities.NewClient