sigs.k8s.io/cluster-api/bootstrap/kubeadm@v0.0.0-20191016155141-23a891785b60/controllers/kubeadmconfig_controller.go (about)

     1  /*
     2  Copyright 2019 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  	"strings"
    23  	"time"
    24  
    25  	"github.com/go-logr/logr"
    26  	"github.com/pkg/errors"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    30  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha2"
    31  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/cloudinit"
    32  	internalcluster "sigs.k8s.io/cluster-api/bootstrap/kubeadm/internal/cluster"
    33  	kubeadmv1beta1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/kubeadm/v1beta1"
    34  	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
    35  	capierrors "sigs.k8s.io/cluster-api/errors"
    36  	"sigs.k8s.io/cluster-api/util"
    37  	"sigs.k8s.io/cluster-api/util/patch"
    38  	"sigs.k8s.io/cluster-api/util/secret"
    39  	ctrl "sigs.k8s.io/controller-runtime"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  	"sigs.k8s.io/controller-runtime/pkg/handler"
    42  	"sigs.k8s.io/controller-runtime/pkg/source"
    43  )
    44  
    45  // InitLocker is a lock that is used around kubeadm init
    46  type InitLocker interface {
    47  	Lock(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) bool
    48  	Unlock(ctx context.Context, cluster *clusterv1.Cluster) bool
    49  }
    50  
    51  // SecretsClientFactory define behaviour for creating a secrets client
    52  type SecretsClientFactory interface {
    53  	// NewSecretsClient returns a new client supporting SecretInterface
    54  	NewSecretsClient(client.Client, *clusterv1.Cluster) (typedcorev1.SecretInterface, error)
    55  }
    56  
    57  // +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=kubeadmconfigs;kubeadmconfigs/status,verbs=get;list;watch;create;update;patch;delete
    58  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status;machines;machines/status,verbs=get;list;watch
    59  // +kubebuilder:rbac:groups="",resources=secrets;events;configmaps,verbs=get;list;watch;create;update;patch;delete
    60  
    61  // KubeadmConfigReconciler reconciles a KubeadmConfig object
    62  type KubeadmConfigReconciler struct {
    63  	client.Client
    64  	SecretsClientFactory SecretsClientFactory
    65  	KubeadmInitLock      InitLocker
    66  	Log                  logr.Logger
    67  }
    68  
    69  // SetupWithManager sets up the reconciler with the Manager.
    70  func (r *KubeadmConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
    71  	return ctrl.NewControllerManagedBy(mgr).
    72  		For(&bootstrapv1.KubeadmConfig{}).
    73  		Watches(
    74  			&source.Kind{Type: &clusterv1.Machine{}},
    75  			&handler.EnqueueRequestsFromMapFunc{
    76  				ToRequests: handler.ToRequestsFunc(r.MachineToBootstrapMapFunc),
    77  			},
    78  		).
    79  		Watches(
    80  			&source.Kind{Type: &clusterv1.Cluster{}},
    81  			&handler.EnqueueRequestsFromMapFunc{
    82  				ToRequests: handler.ToRequestsFunc(r.ClusterToKubeadmConfigs),
    83  			},
    84  		).
    85  		Complete(r)
    86  }
    87  
    88  // Reconcile handles KubeadmConfig events
    89  func (r *KubeadmConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr error) {
    90  	ctx := context.Background()
    91  	log := r.Log.WithValues("kubeadmconfig", req.NamespacedName)
    92  
    93  	// Lookup the kubeadm config
    94  	config := &bootstrapv1.KubeadmConfig{}
    95  	if err := r.Get(ctx, req.NamespacedName, config); err != nil {
    96  		if apierrors.IsNotFound(err) {
    97  			return ctrl.Result{}, nil
    98  		}
    99  		log.Error(err, "failed to get config")
   100  		return ctrl.Result{}, err
   101  	}
   102  
   103  	// Look up the Machine that owns this KubeConfig if there is one
   104  	machine, err := util.GetOwnerMachine(ctx, r.Client, config.ObjectMeta)
   105  	if err != nil {
   106  		log.Error(err, "could not get owner machine")
   107  		return ctrl.Result{}, err
   108  	}
   109  	if machine == nil {
   110  		log.Info("Waiting for Machine Controller to set OwnerRef on the KubeadmConfig")
   111  		return ctrl.Result{}, nil
   112  	}
   113  	log = log.WithValues("machine-name", machine.Name)
   114  
   115  	// Lookup the cluster the machine is associated with
   116  	cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta)
   117  	if err != nil {
   118  		if errors.Cause(err) == util.ErrNoCluster {
   119  			log.Info("Machine does not belong to a cluster yet, waiting until its part of a cluster")
   120  			return ctrl.Result{}, nil
   121  		}
   122  
   123  		if apierrors.IsNotFound(err) {
   124  			log.Info("Cluster does not exist yet , waiting until it is created")
   125  			return ctrl.Result{}, nil
   126  		}
   127  		log.Error(err, "could not get cluster by machine metadata")
   128  		return ctrl.Result{}, err
   129  	}
   130  
   131  	switch {
   132  	// Wait patiently for the infrastructure to be ready
   133  	case !cluster.Status.InfrastructureReady:
   134  		log.Info("Infrastructure is not ready, waiting until ready.")
   135  		return ctrl.Result{}, nil
   136  	// bail super early if it's already ready
   137  	case config.Status.Ready && machine.Status.InfrastructureReady:
   138  		log.Info("ignoring config for an already ready machine")
   139  		return ctrl.Result{}, nil
   140  	// Reconcile status for machines that have already copied bootstrap data
   141  	case machine.Spec.Bootstrap.Data != nil && !config.Status.Ready:
   142  		config.Status.Ready = true
   143  		// Initialize the patch helper
   144  		patchHelper, err := patch.NewHelper(config, r)
   145  		if err != nil {
   146  			return ctrl.Result{}, err
   147  		}
   148  		err = patchHelper.Patch(ctx, config)
   149  		return ctrl.Result{}, err
   150  	// If we've already embedded a time-limited join token into a config, but are still waiting for the token to be used, refresh it
   151  	case config.Status.Ready && (config.Spec.JoinConfiguration != nil && config.Spec.JoinConfiguration.Discovery.BootstrapToken != nil):
   152  		token := config.Spec.JoinConfiguration.Discovery.BootstrapToken.Token
   153  
   154  		// gets the remote secret interface client for the current cluster
   155  		secretsClient, err := r.SecretsClientFactory.NewSecretsClient(r.Client, cluster)
   156  		if err != nil {
   157  			return ctrl.Result{}, err
   158  		}
   159  
   160  		log.Info("refreshing token until the infrastructure has a chance to consume it")
   161  		err = refreshToken(secretsClient, token)
   162  		if err != nil {
   163  			// It would be nice to re-create the bootstrap token if the error was "not found", but we have no way to update the Machine's bootstrap data
   164  			return ctrl.Result{}, errors.Wrapf(err, "failed to refresh bootstrap token")
   165  		}
   166  		// NB: this may not be sufficient to keep the token live if we don't see it before it expires, but when we generate a config we will set the status to "ready" which should generate an update event
   167  		return ctrl.Result{
   168  			RequeueAfter: DefaultTokenTTL / 2,
   169  		}, nil
   170  	}
   171  
   172  	// Initialize the patch helper
   173  	patchHelper, err := patch.NewHelper(config, r)
   174  	if err != nil {
   175  		return ctrl.Result{}, err
   176  	}
   177  	// Attempt to Patch the KubeadmConfig object and status after each reconciliation if no error occurs.
   178  	defer func() {
   179  		if rerr == nil {
   180  			if rerr = patchHelper.Patch(ctx, config); rerr != nil {
   181  				log.Error(rerr, "failed to patch config")
   182  			}
   183  		}
   184  	}()
   185  
   186  	if !cluster.Status.ControlPlaneInitialized {
   187  		// if it's NOT a control plane machine, requeue
   188  		if !util.IsControlPlaneMachine(machine) {
   189  			log.Info(fmt.Sprintf("Machine is not a control plane. If it should be a control plane, add `%s: true` as a label to the Machine", clusterv1.MachineControlPlaneLabelName))
   190  			return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
   191  		}
   192  
   193  		// if the machine has not ClusterConfiguration and InitConfiguration, requeue
   194  		if config.Spec.InitConfiguration == nil && config.Spec.ClusterConfiguration == nil {
   195  			log.Info("Control plane is not ready, requeing joining control planes until ready.")
   196  			return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
   197  		}
   198  
   199  		// acquire the init lock so that only the first machine configured
   200  		// as control plane get processed here
   201  		// if not the first, requeue
   202  		if !r.KubeadmInitLock.Lock(ctx, cluster, machine) {
   203  			log.Info("A control plane is already being initialized, requeing until control plane is ready")
   204  			return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
   205  		}
   206  
   207  		defer func() {
   208  			if rerr != nil {
   209  				r.KubeadmInitLock.Unlock(ctx, cluster)
   210  			}
   211  		}()
   212  
   213  		log.Info("Creating BootstrapData for the init control plane")
   214  
   215  		// Nb. in this case JoinConfiguration should not be defined by users, but in case of misconfigurations, CABPK simply ignore it
   216  
   217  		// get both of ClusterConfiguration and InitConfiguration strings to pass to the cloud init control plane generator
   218  		// kubeadm allows one of these values to be empty; CABPK replace missing values with an empty config, so the cloud init generation
   219  		// should not handle special cases.
   220  
   221  		if config.Spec.InitConfiguration == nil {
   222  			config.Spec.InitConfiguration = &kubeadmv1beta1.InitConfiguration{
   223  				TypeMeta: v1.TypeMeta{
   224  					APIVersion: "kubeadm.k8s.io/v1beta1",
   225  					Kind:       "InitConfiguration",
   226  				},
   227  			}
   228  		}
   229  		initdata, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.InitConfiguration)
   230  		if err != nil {
   231  			log.Error(err, "failed to marshal init configuration")
   232  			return ctrl.Result{}, err
   233  		}
   234  
   235  		if config.Spec.ClusterConfiguration == nil {
   236  			config.Spec.ClusterConfiguration = &kubeadmv1beta1.ClusterConfiguration{
   237  				TypeMeta: v1.TypeMeta{
   238  					APIVersion: "kubeadm.k8s.io/v1beta1",
   239  					Kind:       "ClusterConfiguration",
   240  				},
   241  			}
   242  		}
   243  
   244  		// injects into config.ClusterConfiguration values from top level object
   245  		r.reconcileTopLevelObjectSettings(cluster, machine, config)
   246  
   247  		clusterdata, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.ClusterConfiguration)
   248  		if err != nil {
   249  			log.Error(err, "failed to marshal cluster configuration")
   250  			return ctrl.Result{}, err
   251  		}
   252  
   253  		certificates := internalcluster.NewCertificatesForInitialControlPlane(config.Spec.ClusterConfiguration)
   254  		if err := certificates.LookupOrGenerate(ctx, r.Client, cluster, config); err != nil {
   255  			log.Error(err, "unable to lookup or create cluster certificates")
   256  			return ctrl.Result{}, err
   257  		}
   258  
   259  		cloudInitData, err := cloudinit.NewInitControlPlane(&cloudinit.ControlPlaneInput{
   260  			BaseUserData: cloudinit.BaseUserData{
   261  				AdditionalFiles:     config.Spec.Files,
   262  				NTP:                 config.Spec.NTP,
   263  				PreKubeadmCommands:  config.Spec.PreKubeadmCommands,
   264  				PostKubeadmCommands: config.Spec.PostKubeadmCommands,
   265  				Users:               config.Spec.Users,
   266  			},
   267  			InitConfiguration:    initdata,
   268  			ClusterConfiguration: clusterdata,
   269  			Certificates:         certificates,
   270  		})
   271  		if err != nil {
   272  			log.Error(err, "failed to generate cloud init for bootstrap control plane")
   273  			return ctrl.Result{}, err
   274  		}
   275  
   276  		config.Status.BootstrapData = cloudInitData
   277  		config.Status.Ready = true
   278  
   279  		return ctrl.Result{}, nil
   280  	}
   281  
   282  	// Every other case it's a join scenario
   283  	// Nb. in this case ClusterConfiguration and InitConfiguration should not be defined by users, but in case of misconfigurations, CABPK simply ignore them
   284  
   285  	// Unlock any locks that might have been set during init process
   286  	r.KubeadmInitLock.Unlock(ctx, cluster)
   287  
   288  	// if the JoinConfiguration is missing, create a default one
   289  	if config.Spec.JoinConfiguration == nil {
   290  		log.Info("Creating default JoinConfiguration")
   291  		config.Spec.JoinConfiguration = &kubeadmv1beta1.JoinConfiguration{}
   292  	}
   293  
   294  	// it's a control plane join
   295  	if util.IsControlPlaneMachine(machine) {
   296  		if config.Spec.JoinConfiguration.ControlPlane == nil {
   297  			config.Spec.JoinConfiguration.ControlPlane = &kubeadmv1beta1.JoinControlPlane{}
   298  		}
   299  
   300  		certificates := internalcluster.NewCertificatesForJoiningControlPlane()
   301  		if err := certificates.Lookup(ctx, r.Client, cluster); err != nil {
   302  			log.Error(err, "unable to lookup cluster certificates")
   303  			return ctrl.Result{}, err
   304  		}
   305  		if err := certificates.EnsureAllExist(); err != nil {
   306  			return ctrl.Result{}, err
   307  		}
   308  
   309  		// ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster
   310  		if err := r.reconcileDiscovery(cluster, config, certificates); err != nil {
   311  			if requeueErr, ok := errors.Cause(err).(capierrors.HasRequeueAfterError); ok {
   312  				log.Info(err.Error())
   313  				return ctrl.Result{RequeueAfter: requeueErr.GetRequeueAfter()}, nil
   314  			}
   315  			return ctrl.Result{}, err
   316  		}
   317  
   318  		joinData, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.JoinConfiguration)
   319  		if err != nil {
   320  			log.Error(err, "failed to marshal join configuration")
   321  			return ctrl.Result{}, err
   322  		}
   323  
   324  		log.Info("Creating BootstrapData for the join control plane")
   325  		cloudJoinData, err := cloudinit.NewJoinControlPlane(&cloudinit.ControlPlaneJoinInput{
   326  			JoinConfiguration: joinData,
   327  			Certificates:      certificates,
   328  			BaseUserData: cloudinit.BaseUserData{
   329  				AdditionalFiles:     config.Spec.Files,
   330  				NTP:                 config.Spec.NTP,
   331  				PreKubeadmCommands:  config.Spec.PreKubeadmCommands,
   332  				PostKubeadmCommands: config.Spec.PostKubeadmCommands,
   333  				Users:               config.Spec.Users,
   334  			},
   335  		})
   336  		if err != nil {
   337  			log.Error(err, "failed to create a control plane join configuration")
   338  			return ctrl.Result{}, err
   339  		}
   340  
   341  		config.Status.BootstrapData = cloudJoinData
   342  		config.Status.Ready = true
   343  		return ctrl.Result{}, nil
   344  	}
   345  
   346  	// It's a worker join
   347  	certificates := internalcluster.NewCertificatesForWorker(config.Spec.JoinConfiguration.CACertPath)
   348  	if err := certificates.Lookup(ctx, r.Client, cluster); err != nil {
   349  		log.Error(err, "unable to lookup cluster certificates")
   350  		return ctrl.Result{}, err
   351  	}
   352  	if err := certificates.EnsureAllExist(); err != nil {
   353  		log.Error(err, "Missing certificates")
   354  		return ctrl.Result{}, err
   355  	}
   356  
   357  	// ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster
   358  	if err := r.reconcileDiscovery(cluster, config, certificates); err != nil {
   359  		if requeueErr, ok := errors.Cause(err).(capierrors.HasRequeueAfterError); ok {
   360  			log.Info(err.Error())
   361  			return ctrl.Result{RequeueAfter: requeueErr.GetRequeueAfter()}, nil
   362  		}
   363  		return ctrl.Result{}, err
   364  	}
   365  
   366  	joinData, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.JoinConfiguration)
   367  	if err != nil {
   368  		log.Error(err, "failed to marshal join configuration")
   369  		return ctrl.Result{}, err
   370  	}
   371  
   372  	if config.Spec.JoinConfiguration.ControlPlane != nil {
   373  		return ctrl.Result{}, errors.New("Machine is a Worker, but JoinConfiguration.ControlPlane is set in the KubeadmConfig object")
   374  	}
   375  
   376  	log.Info("Creating BootstrapData for the worker node")
   377  
   378  	cloudJoinData, err := cloudinit.NewNode(&cloudinit.NodeInput{
   379  		BaseUserData: cloudinit.BaseUserData{
   380  			AdditionalFiles:     config.Spec.Files,
   381  			NTP:                 config.Spec.NTP,
   382  			PreKubeadmCommands:  config.Spec.PreKubeadmCommands,
   383  			PostKubeadmCommands: config.Spec.PostKubeadmCommands,
   384  			Users:               config.Spec.Users,
   385  		},
   386  		JoinConfiguration: joinData,
   387  	})
   388  	if err != nil {
   389  		log.Error(err, "failed to create a worker join configuration")
   390  		return ctrl.Result{}, err
   391  	}
   392  	config.Status.BootstrapData = cloudJoinData
   393  	config.Status.Ready = true
   394  	return ctrl.Result{}, nil
   395  }
   396  
   397  // ClusterToKubeadmConfigs is a handler.ToRequestsFunc to be used to enqeue
   398  // requests for reconciliation of KubeadmConfigs.
   399  func (r *KubeadmConfigReconciler) ClusterToKubeadmConfigs(o handler.MapObject) []ctrl.Request {
   400  	result := []ctrl.Request{}
   401  
   402  	c, ok := o.Object.(*clusterv1.Cluster)
   403  	if !ok {
   404  		r.Log.Error(errors.Errorf("expected a Cluster but got a %T", o.Object), "failed to get Machine for Cluster")
   405  		return nil
   406  	}
   407  
   408  	selectors := []client.ListOption{
   409  		client.InNamespace(c.Namespace),
   410  		client.MatchingLabels{
   411  			clusterv1.MachineClusterLabelName: c.Name,
   412  		},
   413  	}
   414  
   415  	machineList := &clusterv1.MachineList{}
   416  	if err := r.List(context.Background(), machineList, selectors...); err != nil {
   417  		r.Log.Error(err, "failed to list Machines", "Cluster", c.Name, "Namespace", c.Namespace)
   418  		return nil
   419  	}
   420  
   421  	for _, m := range machineList.Items {
   422  		if m.Spec.Bootstrap.ConfigRef != nil &&
   423  			m.Spec.Bootstrap.ConfigRef.GroupVersionKind() == bootstrapv1.GroupVersion.WithKind("KubeadmConfig") {
   424  			name := client.ObjectKey{Namespace: m.Namespace, Name: m.Spec.Bootstrap.ConfigRef.Name}
   425  			result = append(result, ctrl.Request{NamespacedName: name})
   426  		}
   427  	}
   428  
   429  	return result
   430  }
   431  
   432  // MachineToBootstrapMapFunc is a handler.ToRequestsFunc to be used to enqeue
   433  // request for reconciliation of KubeadmConfig.
   434  func (r *KubeadmConfigReconciler) MachineToBootstrapMapFunc(o handler.MapObject) []ctrl.Request {
   435  	result := []ctrl.Request{}
   436  
   437  	m, ok := o.Object.(*clusterv1.Machine)
   438  	if !ok {
   439  		return nil
   440  	}
   441  	if m.Spec.Bootstrap.ConfigRef != nil && m.Spec.Bootstrap.ConfigRef.GroupVersionKind() == bootstrapv1.GroupVersion.WithKind("KubeadmConfig") {
   442  		name := client.ObjectKey{Namespace: m.Namespace, Name: m.Spec.Bootstrap.ConfigRef.Name}
   443  		result = append(result, ctrl.Request{NamespacedName: name})
   444  	}
   445  	return result
   446  }
   447  
   448  // reconcileDiscovery ensures that config.JoinConfiguration.Discovery is properly set for the joining node.
   449  // The implementation func respect user provided discovery configurations, but in case some of them are missing, a valid BootstrapToken object
   450  // is automatically injected into config.JoinConfiguration.Discovery.
   451  // This allows to simplify configuration UX, by providing the option to delegate to CABPK the configuration of kubeadm join discovery.
   452  func (r *KubeadmConfigReconciler) reconcileDiscovery(cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig, certificates internalcluster.Certificates) error {
   453  	log := r.Log.WithValues("kubeadmconfig", fmt.Sprintf("%s/%s", config.Namespace, config.Name))
   454  
   455  	// if config already contains a file discovery configuration, respect it without further validations
   456  	if config.Spec.JoinConfiguration.Discovery.File != nil {
   457  		return nil
   458  	}
   459  
   460  	// otherwise it is necessary to ensure token discovery is properly configured
   461  	if config.Spec.JoinConfiguration.Discovery.BootstrapToken == nil {
   462  		config.Spec.JoinConfiguration.Discovery.BootstrapToken = &kubeadmv1beta1.BootstrapTokenDiscovery{}
   463  	}
   464  
   465  	// calculate the ca cert hashes if they are not already set
   466  	if len(config.Spec.JoinConfiguration.Discovery.BootstrapToken.CACertHashes) == 0 {
   467  		hashes, err := certificates.GetByPurpose(secret.ClusterCA).Hashes()
   468  		if err != nil {
   469  			log.Error(err, "Unable to generate Cluster CA certificate hashes")
   470  			return err
   471  		}
   472  		config.Spec.JoinConfiguration.Discovery.BootstrapToken.CACertHashes = hashes
   473  	}
   474  
   475  	// if BootstrapToken already contains an APIServerEndpoint, respect it; otherwise inject the APIServerEndpoint endpoint defined in cluster status
   476  	apiServerEndpoint := config.Spec.JoinConfiguration.Discovery.BootstrapToken.APIServerEndpoint
   477  	if apiServerEndpoint == "" {
   478  		if len(cluster.Status.APIEndpoints) == 0 {
   479  			return errors.Wrap(&capierrors.RequeueAfterError{RequeueAfter: 10 * time.Second}, "Waiting for Cluster Controller to set cluster.Status.APIEndpoints")
   480  		}
   481  
   482  		// NB. CABPK only uses the first APIServerEndpoint defined in cluster status if there are multiple defined.
   483  		apiServerEndpoint = fmt.Sprintf("%s:%d", cluster.Status.APIEndpoints[0].Host, cluster.Status.APIEndpoints[0].Port)
   484  		config.Spec.JoinConfiguration.Discovery.BootstrapToken.APIServerEndpoint = apiServerEndpoint
   485  		log.Info("Altering JoinConfiguration.Discovery.BootstrapToken", "APIServerEndpoint", apiServerEndpoint)
   486  	}
   487  
   488  	// if BootstrapToken already contains a token, respect it; otherwise create a new bootstrap token for the node to join
   489  	if config.Spec.JoinConfiguration.Discovery.BootstrapToken.Token == "" {
   490  		// gets the remote secret interface client for the current cluster
   491  		secretsClient, err := r.SecretsClientFactory.NewSecretsClient(r.Client, cluster)
   492  		if err != nil {
   493  			return err
   494  		}
   495  
   496  		token, err := createToken(secretsClient)
   497  		if err != nil {
   498  			return errors.Wrapf(err, "failed to create new bootstrap token")
   499  		}
   500  
   501  		config.Spec.JoinConfiguration.Discovery.BootstrapToken.Token = token
   502  		log.Info("Altering JoinConfiguration.Discovery.BootstrapToken", "Token", token)
   503  	}
   504  
   505  	// If the BootstrapToken does not contain any CACertHashes then force skip CA Verification
   506  	if len(config.Spec.JoinConfiguration.Discovery.BootstrapToken.CACertHashes) == 0 {
   507  		log.Info("No CAs were provided. Falling back to insecure discover method by skipping CA Cert validation")
   508  		config.Spec.JoinConfiguration.Discovery.BootstrapToken.UnsafeSkipCAVerification = true
   509  	}
   510  
   511  	return nil
   512  }
   513  
   514  // reconcileTopLevelObjectSettings injects into config.ClusterConfiguration values from top level objects like cluster and machine.
   515  // The implementation func respect user provided config values, but in case some of them are missing, values from top level objects are used.
   516  func (r *KubeadmConfigReconciler) reconcileTopLevelObjectSettings(cluster *clusterv1.Cluster, machine *clusterv1.Machine, config *bootstrapv1.KubeadmConfig) {
   517  	log := r.Log.WithValues("kubeadmconfig", fmt.Sprintf("%s/%s", config.Namespace, config.Name))
   518  
   519  	// If there are no ControlPlaneEndpoint defined in ClusterConfiguration but there are APIEndpoints defined at cluster level (e.g. the load balancer endpoint),
   520  	// then use cluster APIEndpoints as a control plane endpoint for the K8s cluster
   521  	if config.Spec.ClusterConfiguration.ControlPlaneEndpoint == "" && len(cluster.Status.APIEndpoints) > 0 {
   522  		// NB. CABPK only uses the first APIServerEndpoint defined in cluster status if there are multiple defined.
   523  		config.Spec.ClusterConfiguration.ControlPlaneEndpoint = fmt.Sprintf("%s:%d", cluster.Status.APIEndpoints[0].Host, cluster.Status.APIEndpoints[0].Port)
   524  		log.Info("Altering ClusterConfiguration", "ControlPlaneEndpoint", config.Spec.ClusterConfiguration.ControlPlaneEndpoint)
   525  	}
   526  
   527  	// If there are no ClusterName defined in ClusterConfiguration, use Cluster.Name
   528  	if config.Spec.ClusterConfiguration.ClusterName == "" {
   529  		config.Spec.ClusterConfiguration.ClusterName = cluster.Name
   530  		log.Info("Altering ClusterConfiguration", "ClusterName", config.Spec.ClusterConfiguration.ClusterName)
   531  	}
   532  
   533  	// If there are no Network settings defined in ClusterConfiguration, use ClusterNetwork settings, if defined
   534  	if cluster.Spec.ClusterNetwork != nil {
   535  		if config.Spec.ClusterConfiguration.Networking.DNSDomain == "" && cluster.Spec.ClusterNetwork.ServiceDomain != "" {
   536  			config.Spec.ClusterConfiguration.Networking.DNSDomain = cluster.Spec.ClusterNetwork.ServiceDomain
   537  			log.Info("Altering ClusterConfiguration", "DNSDomain", config.Spec.ClusterConfiguration.Networking.DNSDomain)
   538  		}
   539  		if config.Spec.ClusterConfiguration.Networking.ServiceSubnet == "" &&
   540  			cluster.Spec.ClusterNetwork.Services != nil &&
   541  			len(cluster.Spec.ClusterNetwork.Services.CIDRBlocks) > 0 {
   542  			config.Spec.ClusterConfiguration.Networking.ServiceSubnet = strings.Join(cluster.Spec.ClusterNetwork.Services.CIDRBlocks, "")
   543  			log.Info("Altering ClusterConfiguration", "ServiceSubnet", config.Spec.ClusterConfiguration.Networking.ServiceSubnet)
   544  		}
   545  		if config.Spec.ClusterConfiguration.Networking.PodSubnet == "" &&
   546  			cluster.Spec.ClusterNetwork.Pods != nil &&
   547  			len(cluster.Spec.ClusterNetwork.Pods.CIDRBlocks) > 0 {
   548  			config.Spec.ClusterConfiguration.Networking.PodSubnet = strings.Join(cluster.Spec.ClusterNetwork.Pods.CIDRBlocks, "")
   549  			log.Info("Altering ClusterConfiguration", "PodSubnet", config.Spec.ClusterConfiguration.Networking.PodSubnet)
   550  		}
   551  	}
   552  
   553  	// If there are no KubernetesVersion settings defined in ClusterConfiguration, use Version from machine, if defined
   554  	if config.Spec.ClusterConfiguration.KubernetesVersion == "" && machine.Spec.Version != nil {
   555  		config.Spec.ClusterConfiguration.KubernetesVersion = *machine.Spec.Version
   556  		log.Info("Altering ClusterConfiguration", "KubernetesVersion", config.Spec.ClusterConfiguration.KubernetesVersion)
   557  	}
   558  }