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