sigs.k8s.io/cluster-api-provider-azure@v1.17.0/controllers/azurejson_machine_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/go-logr/logr"
    24  	"github.com/pkg/errors"
    25  	corev1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/client-go/tools/record"
    30  	"k8s.io/utils/ptr"
    31  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    32  	"sigs.k8s.io/cluster-api-provider-azure/azure/scope"
    33  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/identities"
    34  	azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
    35  	"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    36  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    37  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    38  	"sigs.k8s.io/cluster-api/util"
    39  	"sigs.k8s.io/cluster-api/util/annotations"
    40  	"sigs.k8s.io/cluster-api/util/predicates"
    41  	ctrl "sigs.k8s.io/controller-runtime"
    42  	"sigs.k8s.io/controller-runtime/pkg/builder"
    43  	"sigs.k8s.io/controller-runtime/pkg/client"
    44  	"sigs.k8s.io/controller-runtime/pkg/controller"
    45  	"sigs.k8s.io/controller-runtime/pkg/event"
    46  	"sigs.k8s.io/controller-runtime/pkg/handler"
    47  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    48  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    49  )
    50  
    51  // AzureJSONMachineReconciler reconciles Azure json secrets for AzureMachine objects.
    52  type AzureJSONMachineReconciler struct {
    53  	client.Client
    54  	Recorder         record.EventRecorder
    55  	Timeouts         reconciler.Timeouts
    56  	WatchFilterValue string
    57  }
    58  
    59  // SetupWithManager initializes this controller with a manager.
    60  func (r *AzureJSONMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
    61  	_, log, done := tele.StartSpanWithLogger(ctx,
    62  		"controllers.AzureJSONMachineReconciler.SetupWithManager",
    63  	)
    64  	defer done()
    65  
    66  	azureMachineMapper, err := util.ClusterToTypedObjectsMapper(r.Client, &infrav1.AzureMachineList{}, mgr.GetScheme())
    67  	if err != nil {
    68  		return errors.Wrap(err, "failed to create mapper for Cluster to AzureMachines")
    69  	}
    70  
    71  	return ctrl.NewControllerManagedBy(mgr).
    72  		WithOptions(options).
    73  		For(&infrav1.AzureMachine{}).
    74  		WithEventFilter(filterUnclonedMachinesPredicate{log: log}).
    75  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue)).
    76  		Owns(&corev1.Secret{}).
    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  		Watches(
    80  			&clusterv1.Cluster{},
    81  			handler.EnqueueRequestsFromMapFunc(azureMachineMapper),
    82  			builder.WithPredicates(
    83  				predicates.ClusterUnpausedAndInfrastructureReady(log),
    84  				predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue),
    85  			),
    86  		).
    87  		Complete(r)
    88  }
    89  
    90  type filterUnclonedMachinesPredicate struct {
    91  	log logr.Logger
    92  	predicate.Funcs
    93  }
    94  
    95  func (f filterUnclonedMachinesPredicate) Create(e event.CreateEvent) bool {
    96  	return f.Generic(event.GenericEvent(e))
    97  }
    98  
    99  func (f filterUnclonedMachinesPredicate) Update(e event.UpdateEvent) bool {
   100  	return f.Generic(event.GenericEvent{
   101  		Object: e.ObjectNew,
   102  	})
   103  }
   104  
   105  // Generic implements a default GenericEvent filter.
   106  func (f filterUnclonedMachinesPredicate) Generic(e event.GenericEvent) bool {
   107  	if e.Object == nil {
   108  		f.log.Error(nil, "Generic event has no old metadata", "event", e)
   109  		return false
   110  	}
   111  
   112  	// when watching machines, we only care about machines users created one-off
   113  	// outside of machinedeployments/machinesets and using AzureMachineTemplates. if a machine is part of a machineset
   114  	// or machinedeployment, we already created a secret for the template. All machines
   115  	// in the machinedeployment will share that one secret.
   116  	gvk := infrav1.GroupVersion.WithKind("AzureMachineTemplate")
   117  	isClonedFromTemplate := e.Object.GetAnnotations()[clusterv1.TemplateClonedFromGroupKindAnnotation] == gvk.GroupKind().String()
   118  
   119  	return !isClonedFromTemplate
   120  }
   121  
   122  // Reconcile reconciles the Azure json for a specific machine not in a machine deployment.
   123  func (r *AzureJSONMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
   124  	ctx, cancel := context.WithTimeout(ctx, r.Timeouts.DefaultedLoopTimeout())
   125  	defer cancel()
   126  
   127  	ctx, log, done := tele.StartSpanWithLogger(
   128  		ctx,
   129  		"controllers.AzureJSONMachineReconciler.Reconcile",
   130  		tele.KVP("namespace", req.Namespace),
   131  		tele.KVP("name", req.Name),
   132  		tele.KVP("kind", "AzureMachine"),
   133  	)
   134  	defer done()
   135  
   136  	// Fetch the AzureMachine instance
   137  	azureMachine := &infrav1.AzureMachine{}
   138  	err := r.Get(ctx, req.NamespacedName, azureMachine)
   139  	if err != nil {
   140  		if apierrors.IsNotFound(err) {
   141  			log.Info("object was not found")
   142  			return reconcile.Result{}, nil
   143  		}
   144  		return reconcile.Result{}, err
   145  	}
   146  
   147  	// Fetch the Cluster.
   148  	cluster, err := util.GetClusterFromMetadata(ctx, r.Client, azureMachine.ObjectMeta)
   149  	if err != nil {
   150  		return reconcile.Result{}, err
   151  	}
   152  	if cluster == nil {
   153  		log.Info("Cluster Controller has not yet set OwnerRef")
   154  		return reconcile.Result{}, nil
   155  	}
   156  
   157  	log = log.WithValues("cluster", cluster.Name)
   158  
   159  	// Return early if the object or Cluster is paused.
   160  	if annotations.IsPaused(cluster, azureMachine) {
   161  		log.Info("AzureMachine or linked Cluster is marked as paused. Won't reconcile")
   162  		return ctrl.Result{}, nil
   163  	}
   164  
   165  	_, kind := infrav1.GroupVersion.WithKind(infrav1.AzureClusterKind).ToAPIVersionAndKind()
   166  
   167  	// only look at azure clusters
   168  	if cluster.Spec.InfrastructureRef == nil {
   169  		log.Info("infra ref is nil")
   170  		return ctrl.Result{}, nil
   171  	}
   172  	if cluster.Spec.InfrastructureRef.Kind != kind {
   173  		log.WithValues("kind", cluster.Spec.InfrastructureRef.Kind).Info("infra ref was not an AzureCluster")
   174  		return ctrl.Result{}, nil
   175  	}
   176  
   177  	// fetch the corresponding azure cluster
   178  	azureCluster := &infrav1.AzureCluster{}
   179  	azureClusterName := types.NamespacedName{
   180  		Namespace: req.Namespace,
   181  		Name:      cluster.Spec.InfrastructureRef.Name,
   182  	}
   183  
   184  	if err := r.Get(ctx, azureClusterName, azureCluster); err != nil {
   185  		log.Error(err, "failed to fetch AzureCluster")
   186  		return reconcile.Result{}, err
   187  	}
   188  
   189  	// Create the scope.
   190  	clusterScope, err := scope.NewClusterScope(ctx, scope.ClusterScopeParams{
   191  		Client:       r.Client,
   192  		Cluster:      cluster,
   193  		AzureCluster: azureCluster,
   194  		Timeouts:     r.Timeouts,
   195  	})
   196  	if err != nil {
   197  		return reconcile.Result{}, errors.Wrap(err, "failed to create scope")
   198  	}
   199  
   200  	apiVersion, kind := infrav1.GroupVersion.WithKind("AzureMachine").ToAPIVersionAndKind()
   201  	owner := metav1.OwnerReference{
   202  		APIVersion: apiVersion,
   203  		Kind:       kind,
   204  		Name:       azureMachine.GetName(),
   205  		UID:        azureMachine.GetUID(),
   206  		Controller: ptr.To(true),
   207  	}
   208  
   209  	// Construct secret for this machine
   210  	userAssignedIdentityIfExists := ""
   211  	if len(azureMachine.Spec.UserAssignedIdentities) > 0 {
   212  		var identitiesClient identities.Client
   213  		identitiesClient, err := identities.NewClient(clusterScope)
   214  		if err != nil {
   215  			return reconcile.Result{}, errors.Wrap(err, "failed to create identities client")
   216  		}
   217  		parsed, err := azureutil.ParseResourceID(azureMachine.Spec.UserAssignedIdentities[0].ProviderID)
   218  		if err != nil {
   219  			return reconcile.Result{}, errors.Wrapf(err, "failed to parse ProviderID %s", azureMachine.Spec.UserAssignedIdentities[0].ProviderID)
   220  		}
   221  		if parsed.SubscriptionID != clusterScope.SubscriptionID() {
   222  			identitiesClient, err = identities.NewClientBySub(clusterScope, parsed.SubscriptionID)
   223  			if err != nil {
   224  				return reconcile.Result{}, errors.Wrapf(err, "failed to create identities client from subscription ID %s", parsed.SubscriptionID)
   225  			}
   226  		}
   227  		userAssignedIdentityIfExists, err = identitiesClient.GetClientID(
   228  			ctx, azureMachine.Spec.UserAssignedIdentities[0].ProviderID)
   229  		if err != nil {
   230  			return reconcile.Result{}, errors.Wrap(err, "failed to get user-assigned identity ClientID")
   231  		}
   232  	}
   233  
   234  	if azureMachine.Spec.Identity == infrav1.VMIdentityNone {
   235  		log.Info(fmt.Sprintf("WARNING, %s", spIdentityWarning))
   236  		r.Recorder.Eventf(azureMachine, corev1.EventTypeWarning, "VMIdentityNone", spIdentityWarning)
   237  	}
   238  
   239  	newSecret, err := GetCloudProviderSecret(
   240  		clusterScope,
   241  		azureMachine.Namespace,
   242  		azureMachine.Name,
   243  		owner,
   244  		azureMachine.Spec.Identity,
   245  		userAssignedIdentityIfExists,
   246  	)
   247  
   248  	if err != nil {
   249  		return ctrl.Result{}, errors.Wrap(err, "failed to create cloud provider config")
   250  	}
   251  
   252  	if err := reconcileAzureSecret(ctx, r.Client, owner, newSecret, clusterScope.ClusterName()); err != nil {
   253  		r.Recorder.Eventf(azureMachine, corev1.EventTypeWarning, "Error reconciling cloud provider secret for AzureMachine", err.Error())
   254  		return ctrl.Result{}, errors.Wrap(err, "failed to reconcile azure secret")
   255  	}
   256  
   257  	return ctrl.Result{}, nil
   258  }