sigs.k8s.io/cluster-api-provider-azure@v1.17.0/controllers/agentpooladopt_controller.go (about)

     1  /*
     2  Copyright 2024 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  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/utils/ptr"
    28  	infrav1alpha "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha1"
    29  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    32  	ctrl "sigs.k8s.io/controller-runtime"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  	"sigs.k8s.io/controller-runtime/pkg/controller"
    35  	"sigs.k8s.io/controller-runtime/pkg/event"
    36  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    37  )
    38  
    39  // AgentPoolAdoptReconciler adopts ASO ManagedClustersAgentPool resources into a CAPI Cluster.
    40  type AgentPoolAdoptReconciler struct {
    41  	client.Client
    42  }
    43  
    44  // SetupWithManager sets up the controller with the Manager.
    45  func (r *AgentPoolAdoptReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
    46  	_, err := ctrl.NewControllerManagedBy(mgr).
    47  		WithOptions(options).
    48  		For(&asocontainerservicev1.ManagedClustersAgentPool{}).
    49  		WithEventFilter(predicate.Funcs{
    50  			UpdateFunc: func(ev event.UpdateEvent) bool {
    51  				return ev.ObjectOld.GetAnnotations()[adoptAnnotation] != ev.ObjectNew.GetAnnotations()[adoptAnnotation]
    52  			},
    53  			DeleteFunc: func(_ event.DeleteEvent) bool { return false },
    54  		}).
    55  		Build(r)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools,verbs=create
    64  
    65  // Reconcile reconciles an AzureASOManagedCluster.
    66  func (r *AgentPoolAdoptReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, resultErr error) {
    67  	ctx, log, done := tele.StartSpanWithLogger(ctx,
    68  		"controllers.AgentPoolAdoptReconciler.Reconcile",
    69  		tele.KVP("namespace", req.Namespace),
    70  		tele.KVP("name", req.Name),
    71  		tele.KVP("kind", "ManagedCluster"),
    72  	)
    73  	defer done()
    74  
    75  	agentPool := &asocontainerservicev1.ManagedClustersAgentPool{}
    76  	err := r.Get(ctx, req.NamespacedName, agentPool)
    77  	if err != nil {
    78  		return ctrl.Result{}, client.IgnoreNotFound(err)
    79  	}
    80  
    81  	if agentPool.GetAnnotations()[adoptAnnotation] != adoptAnnotationValue {
    82  		return ctrl.Result{}, nil
    83  	}
    84  
    85  	for _, owner := range agentPool.GetOwnerReferences() {
    86  		if owner.APIVersion == infrav1alpha.GroupVersion.Identifier() &&
    87  			owner.Kind == infrav1alpha.AzureASOManagedMachinePoolKind {
    88  			return ctrl.Result{}, nil
    89  		}
    90  	}
    91  
    92  	log.Info("adopting")
    93  
    94  	namespace := agentPool.Namespace
    95  
    96  	// filter down to what will be persisted in the AzureASOManagedMachinePool
    97  	agentPool = &asocontainerservicev1.ManagedClustersAgentPool{
    98  		TypeMeta: metav1.TypeMeta{
    99  			APIVersion: asocontainerservicev1.GroupVersion.Identifier(),
   100  			Kind:       "ManagedClustersAgentPool",
   101  		},
   102  		ObjectMeta: metav1.ObjectMeta{
   103  			Name: agentPool.Name,
   104  		},
   105  		Spec: agentPool.Spec,
   106  	}
   107  
   108  	var replicas *int32
   109  	if agentPool.Spec.Count != nil {
   110  		replicas = ptr.To(int32(*agentPool.Spec.Count))
   111  		agentPool.Spec.Count = nil
   112  	}
   113  
   114  	managedCluster := &asocontainerservicev1.ManagedCluster{}
   115  	if agentPool.Owner() == nil {
   116  		return ctrl.Result{}, fmt.Errorf("agent pool %s/%s has no owner", namespace, agentPool.Name)
   117  	}
   118  	managedClusterKey := client.ObjectKey{
   119  		Namespace: namespace,
   120  		Name:      agentPool.Owner().Name,
   121  	}
   122  	err = r.Get(ctx, managedClusterKey, managedCluster)
   123  	if err != nil {
   124  		return ctrl.Result{}, fmt.Errorf("failed to get ManagedCluster %s: %w", managedClusterKey, err)
   125  	}
   126  	var managedControlPlaneOwner *metav1.OwnerReference
   127  	for _, owner := range managedCluster.GetOwnerReferences() {
   128  		if owner.APIVersion == infrav1alpha.GroupVersion.Identifier() &&
   129  			owner.Kind == infrav1alpha.AzureASOManagedControlPlaneKind &&
   130  			owner.Name == agentPool.Owner().Name {
   131  			managedControlPlaneOwner = ptr.To(owner)
   132  			break
   133  		}
   134  	}
   135  	if managedControlPlaneOwner == nil {
   136  		return ctrl.Result{}, fmt.Errorf("ManagedCluster %s is not owned by any AzureASOManagedControlPlane", managedClusterKey)
   137  	}
   138  	asoManagedControlPlane := &infrav1alpha.AzureASOManagedControlPlane{}
   139  	managedControlPlaneKey := client.ObjectKey{
   140  		Namespace: namespace,
   141  		Name:      managedControlPlaneOwner.Name,
   142  	}
   143  	err = r.Get(ctx, managedControlPlaneKey, asoManagedControlPlane)
   144  	if err != nil {
   145  		return ctrl.Result{}, fmt.Errorf("failed to get AzureASOManagedControlPlane %s: %w", managedControlPlaneKey, err)
   146  	}
   147  	clusterName := asoManagedControlPlane.Labels[clusterv1.ClusterNameLabel]
   148  
   149  	asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   150  		ObjectMeta: metav1.ObjectMeta{
   151  			Namespace: namespace,
   152  			Name:      agentPool.Name,
   153  		},
   154  		Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   155  			AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   156  				Resources: []runtime.RawExtension{
   157  					{Object: agentPool},
   158  				},
   159  			},
   160  		},
   161  	}
   162  
   163  	machinePool := &expv1.MachinePool{
   164  		ObjectMeta: metav1.ObjectMeta{
   165  			Namespace: namespace,
   166  			Name:      agentPool.Name,
   167  		},
   168  		Spec: expv1.MachinePoolSpec{
   169  			ClusterName: clusterName,
   170  			Replicas:    replicas,
   171  			Template: clusterv1.MachineTemplateSpec{
   172  				Spec: clusterv1.MachineSpec{
   173  					Bootstrap: clusterv1.Bootstrap{
   174  						DataSecretName: ptr.To(""),
   175  					},
   176  					ClusterName: clusterName,
   177  					InfrastructureRef: corev1.ObjectReference{
   178  						APIVersion: infrav1alpha.GroupVersion.Identifier(),
   179  						Kind:       infrav1alpha.AzureASOManagedMachinePoolKind,
   180  						Name:       asoManagedMachinePool.Name,
   181  					},
   182  				},
   183  			},
   184  		},
   185  	}
   186  
   187  	if ptr.Deref(agentPool.Spec.EnableAutoScaling, false) {
   188  		machinePool.Annotations = map[string]string{
   189  			clusterv1.ReplicasManagedByAnnotation: infrav1alpha.ReplicasManagedByAKS,
   190  		}
   191  	}
   192  
   193  	err = r.Create(ctx, machinePool)
   194  	if client.IgnoreAlreadyExists(err) != nil {
   195  		return ctrl.Result{}, err
   196  	}
   197  
   198  	err = r.Create(ctx, asoManagedMachinePool)
   199  	if client.IgnoreAlreadyExists(err) != nil {
   200  		return ctrl.Result{}, err
   201  	}
   202  
   203  	return ctrl.Result{}, nil
   204  }