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