sigs.k8s.io/cluster-api-provider-aws@v1.5.5/bootstrap/eks/controllers/eksconfig_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  	"bytes"
    21  	"context"
    22  	"fmt"
    23  
    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/runtime"
    29  	"k8s.io/utils/pointer"
    30  	ctrl "sigs.k8s.io/controller-runtime"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/controller-runtime/pkg/controller"
    33  	"sigs.k8s.io/controller-runtime/pkg/handler"
    34  	"sigs.k8s.io/controller-runtime/pkg/source"
    35  
    36  	eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/api/v1beta1"
    37  	"sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/internal/userdata"
    38  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    39  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    40  	bsutil "sigs.k8s.io/cluster-api/bootstrap/util"
    41  	expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    42  	"sigs.k8s.io/cluster-api/feature"
    43  	"sigs.k8s.io/cluster-api/util"
    44  	"sigs.k8s.io/cluster-api/util/annotations"
    45  	"sigs.k8s.io/cluster-api/util/conditions"
    46  	"sigs.k8s.io/cluster-api/util/patch"
    47  	"sigs.k8s.io/cluster-api/util/predicates"
    48  )
    49  
    50  // EKSConfigReconciler reconciles a EKSConfig object.
    51  type EKSConfigReconciler struct {
    52  	client.Client
    53  	Scheme           *runtime.Scheme
    54  	WatchFilterValue string
    55  }
    56  
    57  // +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=eksconfigs,verbs=get;list;watch;create;update;patch;delete
    58  // +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=eksconfigs/status,verbs=get;update;patch
    59  // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=awsmanagedcontrolplanes,verbs=get;list;watch
    60  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines;machinepools;clusters,verbs=get;list;watch
    61  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools,verbs=get;list;watch
    62  // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete;
    63  
    64  func (r *EKSConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, rerr error) {
    65  	log := ctrl.LoggerFrom(ctx)
    66  
    67  	// get EKSConfig
    68  	config := &eksbootstrapv1.EKSConfig{}
    69  	if err := r.Client.Get(ctx, req.NamespacedName, config); err != nil {
    70  		if apierrors.IsNotFound(err) {
    71  			return ctrl.Result{}, nil
    72  		}
    73  		log.Error(err, "Failed to get config")
    74  		return ctrl.Result{}, err
    75  	}
    76  
    77  	// check owner references and look up owning Machine object
    78  	configOwner, err := bsutil.GetConfigOwner(ctx, r.Client, config)
    79  	if apierrors.IsNotFound(err) {
    80  		// no error here, requeue until we find an owner
    81  		return ctrl.Result{}, nil
    82  	}
    83  	if err != nil {
    84  		log.Error(err, "Failed to get owner")
    85  		return ctrl.Result{}, err
    86  	}
    87  	if configOwner == nil {
    88  		// no error, requeue until we find an owner
    89  		return ctrl.Result{}, nil
    90  	}
    91  
    92  	log = log.WithValues(configOwner.GetKind(), configOwner.GetName())
    93  
    94  	cluster, err := util.GetClusterByName(ctx, r.Client, configOwner.GetNamespace(), configOwner.ClusterName())
    95  	if err != nil {
    96  		if errors.Is(err, util.ErrNoCluster) {
    97  			log.Info("EKSConfig does not belong to a cluster yet, re-queuing until it's partof a cluster")
    98  			return ctrl.Result{}, nil
    99  		}
   100  		if apierrors.IsNotFound(err) {
   101  			log.Info("Cluster does not exist yet, re-queueing until it is created")
   102  			return ctrl.Result{}, nil
   103  		}
   104  		log.Error(err, "Could not get cluster with metadata")
   105  		return ctrl.Result{}, err
   106  	}
   107  	log = log.WithValues("cluster", cluster.Name)
   108  
   109  	if annotations.IsPaused(cluster, config) {
   110  		log.Info("Reconciliation is paused for this object")
   111  		return ctrl.Result{}, nil
   112  	}
   113  
   114  	patchHelper, err := patch.NewHelper(config, r.Client)
   115  	if err != nil {
   116  		return ctrl.Result{}, err
   117  	}
   118  
   119  	// set up defer block for updating config
   120  	defer func() {
   121  		conditions.SetSummary(config,
   122  			conditions.WithConditions(
   123  				eksbootstrapv1.DataSecretAvailableCondition,
   124  			),
   125  			conditions.WithStepCounter(),
   126  		)
   127  
   128  		patchOpts := []patch.Option{}
   129  		if rerr == nil {
   130  			patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
   131  		}
   132  		if err := patchHelper.Patch(ctx, config, patchOpts...); err != nil {
   133  			log.Error(rerr, "Failed to patch config")
   134  			if rerr == nil {
   135  				rerr = err
   136  			}
   137  		}
   138  	}()
   139  
   140  	return r.joinWorker(ctx, cluster, config)
   141  }
   142  
   143  func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1.Cluster, config *eksbootstrapv1.EKSConfig) (ctrl.Result, error) {
   144  	log := ctrl.LoggerFrom(ctx)
   145  
   146  	if config.Status.DataSecretName != nil {
   147  		secretKey := client.ObjectKey{Namespace: config.Namespace, Name: *config.Status.DataSecretName}
   148  		log = log.WithValues("data-secret-name", secretKey.Name)
   149  		existingSecret := &corev1.Secret{}
   150  
   151  		// No error here means the Secret exists and we have no
   152  		// reason to proceed.
   153  		err := r.Client.Get(ctx, secretKey, existingSecret)
   154  		switch {
   155  		case err == nil:
   156  			return ctrl.Result{}, nil
   157  		case !apierrors.IsNotFound(err):
   158  			log.Error(err, "unable to check for existing bootstrap secret")
   159  			return ctrl.Result{}, err
   160  		}
   161  	}
   162  
   163  	if cluster.Spec.ControlPlaneRef == nil || cluster.Spec.ControlPlaneRef.Kind != "AWSManagedControlPlane" {
   164  		return ctrl.Result{}, errors.New("Cluster's controlPlaneRef needs to be an AWSManagedControlPlane in order to use the EKS bootstrap provider")
   165  	}
   166  
   167  	if !cluster.Status.InfrastructureReady {
   168  		log.Info("Cluster infrastructure is not ready")
   169  		conditions.MarkFalse(config,
   170  			eksbootstrapv1.DataSecretAvailableCondition,
   171  			eksbootstrapv1.WaitingForClusterInfrastructureReason,
   172  			clusterv1.ConditionSeverityInfo, "")
   173  		return ctrl.Result{}, nil
   174  	}
   175  
   176  	if !conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) {
   177  		log.Info("Control Plane has not yet been initialized")
   178  		conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.WaitingForControlPlaneInitializationReason, clusterv1.ConditionSeverityInfo, "")
   179  		return ctrl.Result{}, nil
   180  	}
   181  
   182  	controlPlane := &ekscontrolplanev1.AWSManagedControlPlane{}
   183  	if err := r.Get(ctx, client.ObjectKey{Name: cluster.Spec.ControlPlaneRef.Name, Namespace: cluster.Spec.ControlPlaneRef.Namespace}, controlPlane); err != nil {
   184  		return ctrl.Result{}, err
   185  	}
   186  
   187  	log.Info("Generating userdata")
   188  
   189  	nodeInput := &userdata.NodeInput{
   190  		// AWSManagedControlPlane webhooks default and validate EKSClusterName
   191  		ClusterName:      controlPlane.Spec.EKSClusterName,
   192  		KubeletExtraArgs: config.Spec.KubeletExtraArgs,
   193  		ContainerRuntime: config.Spec.ContainerRuntime,
   194  		DNSClusterIP:     config.Spec.DNSClusterIP,
   195  		DockerConfigJSON: config.Spec.DockerConfigJSON,
   196  		APIRetryAttempts: config.Spec.APIRetryAttempts,
   197  		UseMaxPods:       config.Spec.UseMaxPods,
   198  	}
   199  	if config.Spec.PauseContainer != nil {
   200  		nodeInput.PauseContainerAccount = &config.Spec.PauseContainer.AccountNumber
   201  		nodeInput.PauseContainerVersion = &config.Spec.PauseContainer.Version
   202  	}
   203  	// TODO(richardcase): uncomment when we support ipv6 / dual stack
   204  	/*if config.Spec.ServiceIPV6Cidr != nil && *config.Spec.ServiceIPV6Cidr != "" {
   205  		nodeInput.ServiceIPV6Cidr = config.Spec.ServiceIPV6Cidr
   206  		nodeInput.IPFamily = pointer.String("ipv6")
   207  	}*/
   208  
   209  	// generate userdata
   210  	userDataScript, err := userdata.NewNode(nodeInput)
   211  	if err != nil {
   212  		log.Error(err, "Failed to create a worker join configuration")
   213  		conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, "")
   214  		return ctrl.Result{}, err
   215  	}
   216  
   217  	// store userdata as secret
   218  	if err := r.storeBootstrapData(ctx, cluster, config, userDataScript); err != nil {
   219  		log.Error(err, "Failed to store bootstrap data")
   220  		conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, "")
   221  		return ctrl.Result{}, err
   222  	}
   223  
   224  	return ctrl.Result{}, nil
   225  }
   226  
   227  func (r *EKSConfigReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, option controller.Options) error {
   228  	b := ctrl.NewControllerManagedBy(mgr).
   229  		For(&eksbootstrapv1.EKSConfig{}).
   230  		WithOptions(option).
   231  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
   232  		Watches(
   233  			&source.Kind{Type: &clusterv1.Machine{}},
   234  			handler.EnqueueRequestsFromMapFunc(r.MachineToBootstrapMapFunc),
   235  		)
   236  
   237  	if feature.Gates.Enabled(feature.MachinePool) {
   238  		b = b.Watches(
   239  			&source.Kind{Type: &expclusterv1.MachinePool{}},
   240  			handler.EnqueueRequestsFromMapFunc(r.MachinePoolToBootstrapMapFunc),
   241  		)
   242  	}
   243  
   244  	c, err := b.Build(r)
   245  	if err != nil {
   246  		return errors.Wrap(err, "failed setting up with a controller manager")
   247  	}
   248  
   249  	err = c.Watch(
   250  		&source.Kind{Type: &clusterv1.Cluster{}},
   251  		handler.EnqueueRequestsFromMapFunc((r.ClusterToEKSConfigs)),
   252  		predicates.ClusterUnpausedAndInfrastructureReady(ctrl.LoggerFrom(ctx)),
   253  	)
   254  	if err != nil {
   255  		return errors.Wrap(err, "failed adding watch for Clusters to controller manager")
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  // storeBootstrapData creates a new secret with the data passed in as input,
   262  // sets the reference in the configuration status and ready to true.
   263  func (r *EKSConfigReconciler) storeBootstrapData(ctx context.Context, cluster *clusterv1.Cluster, config *eksbootstrapv1.EKSConfig, data []byte) error {
   264  	log := ctrl.LoggerFrom(ctx)
   265  
   266  	// as secret creation and scope.Config status patch are not atomic operations
   267  	// it is possible that secret creation happens but the config.Status patches are not applied
   268  	secret := &corev1.Secret{}
   269  	if err := r.Client.Get(ctx, client.ObjectKey{
   270  		Name:      config.Name,
   271  		Namespace: config.Namespace,
   272  	}, secret); err != nil {
   273  		if apierrors.IsNotFound(err) {
   274  			if err := r.createBootstrapSecret(ctx, cluster, config, data); err != nil {
   275  				return errors.Wrap(err, "failed to create bootstrap data secret for EKSConfig")
   276  			}
   277  			log.Info("created bootstrap data secret for EKSConfig", "secret", secret.Name)
   278  		} else {
   279  			return errors.Wrap(err, "failed to get data secret for EKSConfig")
   280  		}
   281  	} else {
   282  		updated, err := r.updateBootstrapSecret(ctx, secret, data)
   283  		if err != nil {
   284  			return errors.Wrap(err, "failed to update data secret for EKSConfig")
   285  		}
   286  		if updated {
   287  			log.Info("updated bootstrap data secret for EKSConfig", "secret", secret.Name)
   288  		} else {
   289  			log.V(4).Info("no change in bootstrap data secret for EKSConfig", "secret", secret.Name)
   290  		}
   291  	}
   292  
   293  	config.Status.DataSecretName = pointer.StringPtr(secret.Name)
   294  	config.Status.Ready = true
   295  	conditions.MarkTrue(config, eksbootstrapv1.DataSecretAvailableCondition)
   296  	return nil
   297  }
   298  
   299  // MachineToBootstrapMapFunc is a handler.ToRequestsFunc to be used to enqueue requests
   300  // for EKSConfig reconciliation.
   301  func (r *EKSConfigReconciler) MachineToBootstrapMapFunc(o client.Object) []ctrl.Request {
   302  	result := []ctrl.Request{}
   303  
   304  	m, ok := o.(*clusterv1.Machine)
   305  	if !ok {
   306  		panic(fmt.Sprintf("Expected a Machine but got a %T", o))
   307  	}
   308  	if m.Spec.Bootstrap.ConfigRef != nil && m.Spec.Bootstrap.ConfigRef.GroupVersionKind() == eksbootstrapv1.GroupVersion.WithKind("EKSConfig") {
   309  		name := client.ObjectKey{Namespace: m.Namespace, Name: m.Spec.Bootstrap.ConfigRef.Name}
   310  		result = append(result, ctrl.Request{NamespacedName: name})
   311  	}
   312  	return result
   313  }
   314  
   315  // MachinePoolToBootstrapMapFunc is a handler.ToRequestsFunc to be uses to enqueue requests
   316  // for EKSConfig reconciliation.
   317  func (r *EKSConfigReconciler) MachinePoolToBootstrapMapFunc(o client.Object) []ctrl.Request {
   318  	result := []ctrl.Request{}
   319  
   320  	m, ok := o.(*expclusterv1.MachinePool)
   321  	if !ok {
   322  		panic(fmt.Sprintf("Expected a MachinePool but got a %T", o))
   323  	}
   324  	configRef := m.Spec.Template.Spec.Bootstrap.ConfigRef
   325  	if configRef != nil && configRef.GroupVersionKind().GroupKind() == eksbootstrapv1.GroupVersion.WithKind("EKSConfig").GroupKind() {
   326  		name := client.ObjectKey{Namespace: m.Namespace, Name: configRef.Name}
   327  		result = append(result, ctrl.Request{NamespacedName: name})
   328  	}
   329  
   330  	return result
   331  }
   332  
   333  // ClusterToEKSConfigs is a handler.ToRequestsFunc to be used to enqueue requests for
   334  // EKSConfig reconciliation.
   335  func (r *EKSConfigReconciler) ClusterToEKSConfigs(o client.Object) []ctrl.Request {
   336  	result := []ctrl.Request{}
   337  
   338  	c, ok := o.(*clusterv1.Cluster)
   339  	if !ok {
   340  		panic(fmt.Sprintf("Expected a Cluster but got a %T", o))
   341  	}
   342  
   343  	selectors := []client.ListOption{
   344  		client.InNamespace(c.Namespace),
   345  		client.MatchingLabels{
   346  			clusterv1.ClusterLabelName: c.Name,
   347  		},
   348  	}
   349  
   350  	machineList := &clusterv1.MachineList{}
   351  	if err := r.Client.List(context.Background(), machineList, selectors...); err != nil {
   352  		return nil
   353  	}
   354  
   355  	for _, m := range machineList.Items {
   356  		if m.Spec.Bootstrap.ConfigRef != nil &&
   357  			m.Spec.Bootstrap.ConfigRef.GroupVersionKind().GroupKind() == eksbootstrapv1.GroupVersion.WithKind("EKSConfig").GroupKind() {
   358  			name := client.ObjectKey{Namespace: m.Namespace, Name: m.Spec.Bootstrap.ConfigRef.Name}
   359  			result = append(result, ctrl.Request{NamespacedName: name})
   360  		}
   361  	}
   362  
   363  	return result
   364  }
   365  
   366  // Create the Secret containing bootstrap userdata.
   367  func (r *EKSConfigReconciler) createBootstrapSecret(ctx context.Context, cluster *clusterv1.Cluster, config *eksbootstrapv1.EKSConfig, data []byte) error {
   368  	secret := &corev1.Secret{
   369  		ObjectMeta: metav1.ObjectMeta{
   370  			Name:      config.Name,
   371  			Namespace: config.Namespace,
   372  			Labels: map[string]string{
   373  				clusterv1.ClusterLabelName: cluster.Name,
   374  			},
   375  			OwnerReferences: []metav1.OwnerReference{
   376  				{
   377  					APIVersion: eksbootstrapv1.GroupVersion.String(),
   378  					Kind:       "EKSConfig",
   379  					Name:       config.Name,
   380  					UID:        config.UID,
   381  					Controller: pointer.BoolPtr(true),
   382  				},
   383  			},
   384  		},
   385  		Data: map[string][]byte{
   386  			"value": data,
   387  		},
   388  		Type: clusterv1.ClusterSecretType,
   389  	}
   390  	return r.Client.Create(ctx, secret)
   391  }
   392  
   393  // Update the userdata in the bootstrap Secret.
   394  func (r *EKSConfigReconciler) updateBootstrapSecret(ctx context.Context, secret *corev1.Secret, data []byte) (bool, error) {
   395  	if !bytes.Equal(secret.Data["value"], data) {
   396  		secret.Data["value"] = data
   397  		return true, r.Client.Update(ctx, secret)
   398  	}
   399  	return false, nil
   400  }