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