sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.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  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"reflect"
    24  	"testing"
    25  	"time"
    26  
    27  	ignition "github.com/flatcar/ignition/config/v2_3"
    28  	"github.com/go-logr/logr"
    29  	. "github.com/onsi/gomega"
    30  	corev1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
    34  	"k8s.io/utils/ptr"
    35  	ctrl "sigs.k8s.io/controller-runtime"
    36  	"sigs.k8s.io/controller-runtime/pkg/client"
    37  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    38  	"sigs.k8s.io/controller-runtime/pkg/log"
    39  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    40  	"sigs.k8s.io/yaml"
    41  
    42  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    43  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    44  	bootstrapbuilder "sigs.k8s.io/cluster-api/bootstrap/kubeadm/internal/builder"
    45  	"sigs.k8s.io/cluster-api/controllers/remote"
    46  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    47  	"sigs.k8s.io/cluster-api/feature"
    48  	"sigs.k8s.io/cluster-api/internal/test/builder"
    49  	"sigs.k8s.io/cluster-api/util"
    50  	"sigs.k8s.io/cluster-api/util/conditions"
    51  	"sigs.k8s.io/cluster-api/util/patch"
    52  	"sigs.k8s.io/cluster-api/util/secret"
    53  )
    54  
    55  // MachineToBootstrapMapFunc return kubeadm bootstrap configref name when configref exists.
    56  func TestKubeadmConfigReconciler_MachineToBootstrapMapFuncReturn(t *testing.T) {
    57  	g := NewWithT(t)
    58  	cluster := builder.Cluster("my-cluster", metav1.NamespaceDefault).Build()
    59  	objs := []client.Object{cluster}
    60  	machineObjs := []client.Object{}
    61  	var expectedConfigName string
    62  	for i := 0; i < 3; i++ {
    63  		configName := fmt.Sprintf("my-config-%d", i)
    64  		m := builder.Machine(metav1.NamespaceDefault, fmt.Sprintf("my-machine-%d", i)).
    65  			WithVersion("v1.19.1").
    66  			WithClusterName(cluster.Name).
    67  			WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "").Unstructured()).
    68  			Build()
    69  		if i == 1 {
    70  			c := newKubeadmConfig(metav1.NamespaceDefault, configName)
    71  			addKubeadmConfigToMachine(c, m)
    72  			objs = append(objs, m, c)
    73  			expectedConfigName = configName
    74  		} else {
    75  			objs = append(objs, m)
    76  		}
    77  		machineObjs = append(machineObjs, m)
    78  	}
    79  	fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build()
    80  	reconciler := &KubeadmConfigReconciler{
    81  		Client:              fakeClient,
    82  		SecretCachingClient: fakeClient,
    83  	}
    84  	for i := 0; i < 3; i++ {
    85  		o := machineObjs[i]
    86  		configs := reconciler.MachineToBootstrapMapFunc(ctx, o)
    87  		if i == 1 {
    88  			g.Expect(configs[0].Name).To(Equal(expectedConfigName))
    89  		} else {
    90  			g.Expect(configs[0].Name).To(Equal(""))
    91  		}
    92  	}
    93  }
    94  
    95  // Reconcile returns early if the kubeadm config is ready because it should never re-generate bootstrap data.
    96  func TestKubeadmConfigReconciler_Reconcile_ReturnEarlyIfKubeadmConfigIsReady(t *testing.T) {
    97  	g := NewWithT(t)
    98  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster1").Build()
    99  	cluster.Status.InfrastructureReady = true
   100  	machine := builder.Machine(metav1.NamespaceDefault, "m1").WithClusterName("cluster1").Build()
   101  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   102  	addKubeadmConfigToMachine(config, machine)
   103  
   104  	config.Status.Ready = true
   105  
   106  	objects := []client.Object{
   107  		cluster,
   108  		machine,
   109  		config,
   110  	}
   111  	myclient := fake.NewClientBuilder().WithObjects(objects...).Build()
   112  
   113  	k := &KubeadmConfigReconciler{
   114  		Client:              myclient,
   115  		SecretCachingClient: myclient,
   116  	}
   117  
   118  	request := ctrl.Request{
   119  		NamespacedName: client.ObjectKey{
   120  			Namespace: metav1.NamespaceDefault,
   121  			Name:      "cfg",
   122  		},
   123  	}
   124  	result, err := k.Reconcile(ctx, request)
   125  	g.Expect(err).ToNot(HaveOccurred())
   126  	g.Expect(result.Requeue).To(BeFalse())
   127  	g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
   128  }
   129  
   130  // Reconcile should update owner references on bootstrap secrets on creation and update.
   131  func TestKubeadmConfigReconciler_TestSecretOwnerReferenceReconciliation(t *testing.T) {
   132  	g := NewWithT(t)
   133  
   134  	clusterName := "my-cluster"
   135  	cluster := builder.Cluster(metav1.NamespaceDefault, clusterName).Build()
   136  	machine := builder.Machine(metav1.NamespaceDefault, "machine").
   137  		WithVersion("v1.19.1").
   138  		WithClusterName(clusterName).
   139  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
   140  		Build()
   141  	machine.Spec.Bootstrap.DataSecretName = ptr.To("something")
   142  
   143  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   144  	config.SetOwnerReferences(util.EnsureOwnerRef(config.GetOwnerReferences(), metav1.OwnerReference{
   145  		APIVersion: clusterv1.GroupVersion.String(),
   146  		Kind:       "Machine",
   147  		Name:       machine.Name,
   148  		UID:        machine.UID,
   149  	}))
   150  	secret := &corev1.Secret{
   151  		ObjectMeta: metav1.ObjectMeta{
   152  			Name:      config.Name,
   153  			Namespace: config.Namespace,
   154  		},
   155  		Type: corev1.SecretTypeBootstrapToken,
   156  	}
   157  	config.Status.Ready = true
   158  
   159  	objects := []client.Object{
   160  		config,
   161  		machine,
   162  		secret,
   163  		cluster,
   164  	}
   165  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   166  
   167  	k := &KubeadmConfigReconciler{
   168  		Client:              myclient,
   169  		SecretCachingClient: myclient,
   170  	}
   171  
   172  	request := ctrl.Request{
   173  		NamespacedName: client.ObjectKey{
   174  			Namespace: metav1.NamespaceDefault,
   175  			Name:      "cfg",
   176  		},
   177  	}
   178  	var err error
   179  	key := client.ObjectKeyFromObject(config)
   180  	actual := &corev1.Secret{}
   181  
   182  	t.Run("KubeadmConfig ownerReference is added on first reconcile", func(*testing.T) {
   183  		_, err = k.Reconcile(ctx, request)
   184  		g.Expect(err).ToNot(HaveOccurred())
   185  
   186  		g.Expect(myclient.Get(ctx, key, actual)).To(Succeed())
   187  
   188  		controllerOwner := metav1.GetControllerOf(actual)
   189  		g.Expect(controllerOwner).To(Not(BeNil()))
   190  		g.Expect(controllerOwner.Kind).To(Equal(config.Kind))
   191  		g.Expect(controllerOwner.Name).To(Equal(config.Name))
   192  	})
   193  
   194  	t.Run("KubeadmConfig ownerReference re-reconciled without error", func(*testing.T) {
   195  		_, err = k.Reconcile(ctx, request)
   196  		g.Expect(err).ToNot(HaveOccurred())
   197  
   198  		g.Expect(myclient.Get(ctx, key, actual)).To(Succeed())
   199  
   200  		controllerOwner := metav1.GetControllerOf(actual)
   201  		g.Expect(controllerOwner).To(Not(BeNil()))
   202  		g.Expect(controllerOwner.Kind).To(Equal(config.Kind))
   203  		g.Expect(controllerOwner.Name).To(Equal(config.Name))
   204  	})
   205  	t.Run("non-KubeadmConfig controller OwnerReference is replaced", func(*testing.T) {
   206  		g.Expect(myclient.Get(ctx, key, actual)).To(Succeed())
   207  
   208  		actual.SetOwnerReferences([]metav1.OwnerReference{
   209  			{
   210  				APIVersion: clusterv1.GroupVersion.String(),
   211  				Kind:       "Machine",
   212  				Name:       machine.Name,
   213  				UID:        machine.UID,
   214  				Controller: ptr.To(true),
   215  			}})
   216  		g.Expect(myclient.Update(ctx, actual)).To(Succeed())
   217  
   218  		_, err = k.Reconcile(ctx, request)
   219  		g.Expect(err).ToNot(HaveOccurred())
   220  
   221  		g.Expect(myclient.Get(ctx, key, actual)).To(Succeed())
   222  
   223  		controllerOwner := metav1.GetControllerOf(actual)
   224  		g.Expect(controllerOwner).To(Not(BeNil()))
   225  		g.Expect(controllerOwner.Kind).To(Equal(config.Kind))
   226  		g.Expect(controllerOwner.Name).To(Equal(config.Name))
   227  	})
   228  }
   229  
   230  // Reconcile returns nil if the referenced Machine cannot be found.
   231  func TestKubeadmConfigReconciler_Reconcile_ReturnNilIfReferencedMachineIsNotFound(t *testing.T) {
   232  	g := NewWithT(t)
   233  
   234  	machine := builder.Machine(metav1.NamespaceDefault, "machine").
   235  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
   236  		WithVersion("v1.19.1").
   237  		Build()
   238  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   239  	addKubeadmConfigToMachine(config, machine)
   240  	objects := []client.Object{
   241  		// intentionally omitting machine
   242  		config,
   243  	}
   244  	myclient := fake.NewClientBuilder().WithObjects(objects...).Build()
   245  
   246  	k := &KubeadmConfigReconciler{
   247  		Client:              myclient,
   248  		SecretCachingClient: myclient,
   249  	}
   250  
   251  	request := ctrl.Request{
   252  		NamespacedName: client.ObjectKey{
   253  			Namespace: metav1.NamespaceDefault,
   254  			Name:      "cfg",
   255  		},
   256  	}
   257  	_, err := k.Reconcile(ctx, request)
   258  	g.Expect(err).ToNot(HaveOccurred())
   259  }
   260  
   261  // If the machine has bootstrap data secret reference, there is no need to generate more bootstrap data.
   262  func TestKubeadmConfigReconciler_Reconcile_ReturnEarlyIfMachineHasDataSecretName(t *testing.T) {
   263  	g := NewWithT(t)
   264  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster1").Build()
   265  	cluster.Status.InfrastructureReady = true
   266  
   267  	machine := builder.Machine(metav1.NamespaceDefault, "machine").
   268  		WithVersion("v1.19.1").
   269  		WithClusterName("cluster1").
   270  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
   271  		Build()
   272  	machine.Spec.Bootstrap.DataSecretName = ptr.To("something")
   273  
   274  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   275  	addKubeadmConfigToMachine(config, machine)
   276  	objects := []client.Object{
   277  		cluster,
   278  		machine,
   279  		config,
   280  	}
   281  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   282  
   283  	k := &KubeadmConfigReconciler{
   284  		Client:              myclient,
   285  		SecretCachingClient: myclient,
   286  	}
   287  
   288  	request := ctrl.Request{
   289  		NamespacedName: client.ObjectKey{
   290  			Namespace: metav1.NamespaceDefault,
   291  			Name:      "cfg",
   292  		},
   293  	}
   294  	result, err := k.Reconcile(ctx, request)
   295  	actual := &bootstrapv1.KubeadmConfig{}
   296  	g.Expect(myclient.Get(ctx, client.ObjectKey{Namespace: config.Namespace, Name: config.Name}, actual)).To(Succeed())
   297  	g.Expect(err).ToNot(HaveOccurred())
   298  	g.Expect(result.Requeue).To(BeFalse())
   299  	g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
   300  	assertHasTrueCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition)
   301  }
   302  
   303  func TestKubeadmConfigReconciler_ReturnEarlyIfClusterInfraNotReady(t *testing.T) {
   304  	g := NewWithT(t)
   305  
   306  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   307  	machine := builder.Machine(metav1.NamespaceDefault, "machine").
   308  		WithVersion("v1.19.1").
   309  		WithClusterName(cluster.Name).
   310  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
   311  		Build()
   312  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   313  	addKubeadmConfigToMachine(config, machine)
   314  
   315  	// cluster infra not ready
   316  	cluster.Status = clusterv1.ClusterStatus{
   317  		InfrastructureReady: false,
   318  	}
   319  
   320  	objects := []client.Object{
   321  		cluster,
   322  		machine,
   323  		config,
   324  	}
   325  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   326  
   327  	k := &KubeadmConfigReconciler{
   328  		Client:              myclient,
   329  		SecretCachingClient: myclient,
   330  	}
   331  
   332  	request := ctrl.Request{
   333  		NamespacedName: client.ObjectKey{
   334  			Namespace: metav1.NamespaceDefault,
   335  			Name:      "cfg",
   336  		},
   337  	}
   338  
   339  	expectedResult := reconcile.Result{}
   340  	actualResult, actualError := k.Reconcile(ctx, request)
   341  	g.Expect(actualResult).To(BeComparableTo(expectedResult))
   342  	g.Expect(actualError).ToNot(HaveOccurred())
   343  	assertHasFalseCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition, clusterv1.ConditionSeverityInfo, bootstrapv1.WaitingForClusterInfrastructureReason)
   344  }
   345  
   346  // Return early If the owning machine does not have an associated cluster.
   347  func TestKubeadmConfigReconciler_Reconcile_ReturnEarlyIfMachineHasNoCluster(t *testing.T) {
   348  	g := NewWithT(t)
   349  	machine := builder.Machine(metav1.NamespaceDefault, "machine").
   350  		WithVersion("v1.19.1").
   351  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
   352  		Build()
   353  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   354  	addKubeadmConfigToMachine(config, machine)
   355  
   356  	objects := []client.Object{
   357  		machine,
   358  		config,
   359  	}
   360  	myclient := fake.NewClientBuilder().WithObjects(objects...).Build()
   361  
   362  	k := &KubeadmConfigReconciler{
   363  		Client:              myclient,
   364  		SecretCachingClient: myclient,
   365  	}
   366  
   367  	request := ctrl.Request{
   368  		NamespacedName: client.ObjectKey{
   369  			Namespace: metav1.NamespaceDefault,
   370  			Name:      "cfg",
   371  		},
   372  	}
   373  	_, err := k.Reconcile(ctx, request)
   374  	g.Expect(err).ToNot(HaveOccurred())
   375  }
   376  
   377  // This does not expect an error, hoping that the associated cluster will be created.
   378  func TestKubeadmConfigReconciler_Reconcile_ReturnNilIfAssociatedClusterIsNotFound(t *testing.T) {
   379  	g := NewWithT(t)
   380  
   381  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   382  	machine := builder.Machine(metav1.NamespaceDefault, "machine").
   383  		WithVersion("v1.19.1").
   384  		WithClusterName(cluster.Name).
   385  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
   386  		Build()
   387  	config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
   388  
   389  	addKubeadmConfigToMachine(config, machine)
   390  	objects := []client.Object{
   391  		// intentionally omitting cluster
   392  		machine,
   393  		config,
   394  	}
   395  	myclient := fake.NewClientBuilder().WithObjects(objects...).Build()
   396  
   397  	k := &KubeadmConfigReconciler{
   398  		Client:              myclient,
   399  		SecretCachingClient: myclient,
   400  	}
   401  
   402  	request := ctrl.Request{
   403  		NamespacedName: client.ObjectKey{
   404  			Namespace: metav1.NamespaceDefault,
   405  			Name:      "cfg",
   406  		},
   407  	}
   408  	_, err := k.Reconcile(ctx, request)
   409  	g.Expect(err).ToNot(HaveOccurred())
   410  }
   411  
   412  // If the control plane isn't initialized then there is no cluster for either a worker or control plane node to join.
   413  func TestKubeadmConfigReconciler_Reconcile_RequeueJoiningNodesIfControlPlaneNotInitialized(t *testing.T) {
   414  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   415  	cluster.Status.InfrastructureReady = true
   416  
   417  	workerMachine := newWorkerMachineForCluster(cluster)
   418  	workerJoinConfig := newWorkerJoinKubeadmConfig(metav1.NamespaceDefault, "worker-join-cfg")
   419  	addKubeadmConfigToMachine(workerJoinConfig, workerMachine)
   420  
   421  	controlPlaneJoinMachine := newControlPlaneMachine(cluster, "control-plane-join-machine")
   422  	controlPlaneJoinConfig := newControlPlaneJoinKubeadmConfig(controlPlaneJoinMachine.Namespace, "control-plane-join-cfg")
   423  	addKubeadmConfigToMachine(controlPlaneJoinConfig, controlPlaneJoinMachine)
   424  
   425  	testcases := []struct {
   426  		name    string
   427  		request ctrl.Request
   428  		objects []client.Object
   429  	}{
   430  		{
   431  			name: "requeue worker when control plane is not yet initialized",
   432  			request: ctrl.Request{
   433  				NamespacedName: client.ObjectKey{
   434  					Namespace: workerJoinConfig.Namespace,
   435  					Name:      workerJoinConfig.Name,
   436  				},
   437  			},
   438  			objects: []client.Object{
   439  				cluster,
   440  				workerMachine,
   441  				workerJoinConfig,
   442  			},
   443  		},
   444  		{
   445  			name: "requeue a secondary control plane when the control plane is not yet initialized",
   446  			request: ctrl.Request{
   447  				NamespacedName: client.ObjectKey{
   448  					Namespace: controlPlaneJoinConfig.Namespace,
   449  					Name:      controlPlaneJoinConfig.Name,
   450  				},
   451  			},
   452  			objects: []client.Object{
   453  				cluster,
   454  				controlPlaneJoinMachine,
   455  				controlPlaneJoinConfig,
   456  			},
   457  		},
   458  	}
   459  	for _, tc := range testcases {
   460  		t.Run(tc.name, func(t *testing.T) {
   461  			g := NewWithT(t)
   462  
   463  			myclient := fake.NewClientBuilder().WithObjects(tc.objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   464  
   465  			k := &KubeadmConfigReconciler{
   466  				Client:              myclient,
   467  				SecretCachingClient: myclient,
   468  				KubeadmInitLock:     &myInitLocker{},
   469  			}
   470  
   471  			result, err := k.Reconcile(ctx, tc.request)
   472  			g.Expect(err).ToNot(HaveOccurred())
   473  			g.Expect(result.Requeue).To(BeFalse())
   474  			g.Expect(result.RequeueAfter).To(Equal(30 * time.Second))
   475  			assertHasFalseCondition(g, myclient, tc.request, bootstrapv1.DataSecretAvailableCondition, clusterv1.ConditionSeverityInfo, clusterv1.WaitingForControlPlaneAvailableReason)
   476  		})
   477  	}
   478  }
   479  
   480  // This generates cloud-config data but does not test the validity of it.
   481  func TestKubeadmConfigReconciler_Reconcile_GenerateCloudConfigData(t *testing.T) {
   482  	g := NewWithT(t)
   483  
   484  	configName := "control-plane-init-cfg"
   485  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   486  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "validhost", Port: 6443}
   487  	cluster.Status.InfrastructureReady = true
   488  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   489  
   490  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
   491  	controlPlaneInitConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, configName)
   492  	controlPlaneInitConfig.Spec.JoinConfiguration = &bootstrapv1.JoinConfiguration{}
   493  	controlPlaneInitConfig.Spec.JoinConfiguration.Discovery.BootstrapToken = &bootstrapv1.BootstrapTokenDiscovery{
   494  		CACertHashes: []string{"...."},
   495  	}
   496  
   497  	addKubeadmConfigToMachine(controlPlaneInitConfig, controlPlaneInitMachine)
   498  
   499  	objects := []client.Object{
   500  		cluster,
   501  		controlPlaneInitMachine,
   502  		controlPlaneInitConfig,
   503  	}
   504  	objects = append(objects, createSecrets(t, cluster, controlPlaneInitConfig)...)
   505  
   506  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   507  
   508  	k := &KubeadmConfigReconciler{
   509  		Client:              myclient,
   510  		SecretCachingClient: myclient,
   511  		Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
   512  		KubeadmInitLock:     &myInitLocker{},
   513  	}
   514  
   515  	request := ctrl.Request{
   516  		NamespacedName: client.ObjectKey{
   517  			Namespace: metav1.NamespaceDefault,
   518  			Name:      "control-plane-init-cfg",
   519  		},
   520  	}
   521  	s := &corev1.Secret{}
   522  	g.Expect(myclient.Get(ctx, client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: configName}, s)).ToNot(Succeed())
   523  
   524  	result, err := k.Reconcile(ctx, request)
   525  	g.Expect(err).ToNot(HaveOccurred())
   526  	g.Expect(result.Requeue).To(BeFalse())
   527  	g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
   528  
   529  	cfg, err := getKubeadmConfig(myclient, "control-plane-init-cfg", metav1.NamespaceDefault)
   530  	g.Expect(err).ToNot(HaveOccurred())
   531  	g.Expect(cfg.Status.Ready).To(BeTrue())
   532  	g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
   533  	g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
   534  	assertHasTrueCondition(g, myclient, request, bootstrapv1.CertificatesAvailableCondition)
   535  	assertHasTrueCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition)
   536  
   537  	// Expect the Secret to exist, and for it to contain some data under the "value" key.
   538  	g.Expect(myclient.Get(ctx, client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: configName}, s)).To(Succeed())
   539  	g.Expect(s.Data["value"]).ToNot(BeEmpty())
   540  	// Ensure that we don't fail trying to refresh any bootstrap tokens
   541  	_, err = k.Reconcile(ctx, request)
   542  	g.Expect(err).ToNot(HaveOccurred())
   543  }
   544  
   545  // If a control plane has no JoinConfiguration, then we will create a default and no error will occur.
   546  func TestKubeadmConfigReconciler_Reconcile_ErrorIfJoiningControlPlaneHasInvalidConfiguration(t *testing.T) {
   547  	g := NewWithT(t)
   548  	// TODO: extract this kind of code into a setup function that puts the state of objects into an initialized controlplane (implies secrets exist)
   549  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   550  	cluster.Status.InfrastructureReady = true
   551  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   552  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
   553  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
   554  	controlPlaneInitConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "control-plane-init-cfg")
   555  	addKubeadmConfigToMachine(controlPlaneInitConfig, controlPlaneInitMachine)
   556  
   557  	controlPlaneJoinMachine := newControlPlaneMachine(cluster, "control-plane-join-machine")
   558  	controlPlaneJoinConfig := newControlPlaneJoinKubeadmConfig(controlPlaneJoinMachine.Namespace, "control-plane-join-cfg")
   559  	controlPlaneJoinConfig.Spec.JoinConfiguration.ControlPlane = nil // Makes controlPlaneJoinConfig invalid for a control plane machine
   560  	addKubeadmConfigToMachine(controlPlaneJoinConfig, controlPlaneJoinMachine)
   561  
   562  	objects := []client.Object{
   563  		cluster,
   564  		controlPlaneJoinMachine,
   565  		controlPlaneJoinConfig,
   566  	}
   567  	objects = append(objects, createSecrets(t, cluster, controlPlaneInitConfig)...)
   568  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   569  
   570  	k := &KubeadmConfigReconciler{
   571  		Client:              myclient,
   572  		SecretCachingClient: myclient,
   573  		Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
   574  		KubeadmInitLock:     &myInitLocker{},
   575  	}
   576  
   577  	request := ctrl.Request{
   578  		NamespacedName: client.ObjectKey{
   579  			Namespace: metav1.NamespaceDefault,
   580  			Name:      controlPlaneJoinConfig.Name,
   581  		},
   582  	}
   583  	_, err := k.Reconcile(ctx, request)
   584  	g.Expect(err).ToNot(HaveOccurred())
   585  	actualConfig := &bootstrapv1.KubeadmConfig{}
   586  	g.Expect(myclient.Get(ctx, client.ObjectKey{Namespace: controlPlaneJoinConfig.Namespace, Name: controlPlaneJoinConfig.Name}, actualConfig)).To(Succeed())
   587  	assertHasTrueCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition)
   588  	assertHasTrueCondition(g, myclient, request, bootstrapv1.CertificatesAvailableCondition)
   589  }
   590  
   591  // If there is no APIEndpoint but everything is ready then requeue in hopes of a new APIEndpoint showing up eventually.
   592  func TestKubeadmConfigReconciler_Reconcile_RequeueIfControlPlaneIsMissingAPIEndpoints(t *testing.T) {
   593  	g := NewWithT(t)
   594  
   595  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   596  	cluster.Status.InfrastructureReady = true
   597  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   598  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
   599  	controlPlaneInitConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "control-plane-init-cfg")
   600  	addKubeadmConfigToMachine(controlPlaneInitConfig, controlPlaneInitMachine)
   601  
   602  	workerMachine := newWorkerMachineForCluster(cluster)
   603  	workerJoinConfig := newWorkerJoinKubeadmConfig(metav1.NamespaceDefault, "worker-join-cfg")
   604  	addKubeadmConfigToMachine(workerJoinConfig, workerMachine)
   605  
   606  	objects := []client.Object{
   607  		cluster,
   608  		workerMachine,
   609  		workerJoinConfig,
   610  	}
   611  	objects = append(objects, createSecrets(t, cluster, controlPlaneInitConfig)...)
   612  
   613  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   614  
   615  	k := &KubeadmConfigReconciler{
   616  		Client:              myclient,
   617  		SecretCachingClient: myclient,
   618  		KubeadmInitLock:     &myInitLocker{},
   619  	}
   620  
   621  	request := ctrl.Request{
   622  		NamespacedName: client.ObjectKey{
   623  			Namespace: metav1.NamespaceDefault,
   624  			Name:      "worker-join-cfg",
   625  		},
   626  	}
   627  	result, err := k.Reconcile(ctx, request)
   628  	g.Expect(err).ToNot(HaveOccurred())
   629  	g.Expect(result.Requeue).To(BeFalse())
   630  	g.Expect(result.RequeueAfter).To(Equal(10 * time.Second))
   631  
   632  	actualConfig := &bootstrapv1.KubeadmConfig{}
   633  	g.Expect(myclient.Get(ctx, client.ObjectKey{Namespace: workerJoinConfig.Namespace, Name: workerJoinConfig.Name}, actualConfig)).To(Succeed())
   634  
   635  	// At this point the DataSecretAvailableCondition should not be set. CertificatesAvailableCondition should be true.
   636  	g.Expect(conditions.Get(actualConfig, bootstrapv1.DataSecretAvailableCondition)).To(BeNil())
   637  	assertHasTrueCondition(g, myclient, request, bootstrapv1.CertificatesAvailableCondition)
   638  }
   639  
   640  func TestReconcileIfJoinCertificatesAvailableConditioninNodesAndControlPlaneIsReady(t *testing.T) {
   641  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   642  	cluster.Status.InfrastructureReady = true
   643  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   644  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
   645  
   646  	useCases := []struct {
   647  		name          string
   648  		machine       *clusterv1.Machine
   649  		configName    string
   650  		configBuilder func(string, string) *bootstrapv1.KubeadmConfig
   651  	}{
   652  		{
   653  			name:          "Join a worker node with a fully compiled kubeadm config object",
   654  			machine:       newWorkerMachineForCluster(cluster),
   655  			configName:    "worker-join-cfg",
   656  			configBuilder: newWorkerJoinKubeadmConfig,
   657  		},
   658  		{
   659  			name:          "Join a worker node  with an empty kubeadm config object (defaults apply)",
   660  			machine:       newWorkerMachineForCluster(cluster),
   661  			configName:    "worker-join-cfg",
   662  			configBuilder: newKubeadmConfig,
   663  		},
   664  		{
   665  			name:          "Join a control plane node with a fully compiled kubeadm config object",
   666  			machine:       newControlPlaneMachine(cluster, "control-plane-join-machine"),
   667  			configName:    "control-plane-join-cfg",
   668  			configBuilder: newControlPlaneJoinKubeadmConfig,
   669  		},
   670  		{
   671  			name:          "Join a control plane node with an empty kubeadm config object (defaults apply)",
   672  			machine:       newControlPlaneMachine(cluster, "control-plane-join-machine"),
   673  			configName:    "control-plane-join-cfg",
   674  			configBuilder: newKubeadmConfig,
   675  		},
   676  	}
   677  
   678  	for _, rt := range useCases {
   679  		rt := rt // pin!
   680  		t.Run(rt.name, func(t *testing.T) {
   681  			g := NewWithT(t)
   682  
   683  			config := rt.configBuilder(rt.machine.Namespace, rt.configName)
   684  			addKubeadmConfigToMachine(config, rt.machine)
   685  
   686  			objects := []client.Object{
   687  				cluster,
   688  				rt.machine,
   689  				config,
   690  			}
   691  			objects = append(objects, createSecrets(t, cluster, config)...)
   692  			myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   693  			k := &KubeadmConfigReconciler{
   694  				Client:              myclient,
   695  				SecretCachingClient: myclient,
   696  				Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
   697  				KubeadmInitLock:     &myInitLocker{},
   698  			}
   699  
   700  			request := ctrl.Request{
   701  				NamespacedName: client.ObjectKey{
   702  					Namespace: config.GetNamespace(),
   703  					Name:      rt.configName,
   704  				},
   705  			}
   706  			result, err := k.Reconcile(ctx, request)
   707  			g.Expect(err).ToNot(HaveOccurred())
   708  			g.Expect(result.Requeue).To(BeFalse())
   709  			g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
   710  
   711  			cfg, err := getKubeadmConfig(myclient, rt.configName, metav1.NamespaceDefault)
   712  			g.Expect(err).ToNot(HaveOccurred())
   713  			g.Expect(cfg.Status.Ready).To(BeTrue())
   714  			g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
   715  			g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
   716  			assertHasTrueCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition)
   717  
   718  			l := &corev1.SecretList{}
   719  			err = myclient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))
   720  			g.Expect(err).ToNot(HaveOccurred())
   721  			g.Expect(l.Items).To(HaveLen(1))
   722  		})
   723  	}
   724  }
   725  
   726  func TestReconcileIfJoinNodePoolsAndControlPlaneIsReady(t *testing.T) {
   727  	_ = feature.MutableGates.Set("MachinePool=true")
   728  
   729  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   730  	cluster.Status.InfrastructureReady = true
   731  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   732  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
   733  
   734  	useCases := []struct {
   735  		name          string
   736  		machinePool   *expv1.MachinePool
   737  		configName    string
   738  		configBuilder func(string, string) *bootstrapv1.KubeadmConfig
   739  	}{
   740  		{
   741  			name:        "Join a worker node with a fully compiled kubeadm config object",
   742  			machinePool: newWorkerMachinePoolForCluster(cluster),
   743  			configName:  "workerpool-join-cfg",
   744  			configBuilder: func(namespace, _ string) *bootstrapv1.KubeadmConfig {
   745  				return newWorkerJoinKubeadmConfig(namespace, "workerpool-join-cfg")
   746  			},
   747  		},
   748  		{
   749  			name:          "Join a worker node  with an empty kubeadm config object (defaults apply)",
   750  			machinePool:   newWorkerMachinePoolForCluster(cluster),
   751  			configName:    "workerpool-join-cfg",
   752  			configBuilder: newKubeadmConfig,
   753  		},
   754  	}
   755  
   756  	for _, rt := range useCases {
   757  		rt := rt // pin!
   758  		t.Run(rt.name, func(t *testing.T) {
   759  			g := NewWithT(t)
   760  
   761  			config := rt.configBuilder(rt.machinePool.Namespace, rt.configName)
   762  			addKubeadmConfigToMachinePool(config, rt.machinePool)
   763  
   764  			objects := []client.Object{
   765  				cluster,
   766  				rt.machinePool,
   767  				config,
   768  			}
   769  			objects = append(objects, createSecrets(t, cluster, config)...)
   770  			myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   771  			k := &KubeadmConfigReconciler{
   772  				Client:              myclient,
   773  				SecretCachingClient: myclient,
   774  				Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
   775  				KubeadmInitLock:     &myInitLocker{},
   776  			}
   777  
   778  			request := ctrl.Request{
   779  				NamespacedName: client.ObjectKey{
   780  					Namespace: config.GetNamespace(),
   781  					Name:      rt.configName,
   782  				},
   783  			}
   784  			result, err := k.Reconcile(ctx, request)
   785  			g.Expect(err).ToNot(HaveOccurred())
   786  			g.Expect(result.Requeue).To(BeFalse())
   787  			g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
   788  
   789  			cfg, err := getKubeadmConfig(myclient, rt.configName, metav1.NamespaceDefault)
   790  			g.Expect(err).ToNot(HaveOccurred())
   791  			g.Expect(cfg.Status.Ready).To(BeTrue())
   792  			g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
   793  			g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
   794  
   795  			l := &corev1.SecretList{}
   796  			err = myclient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))
   797  			g.Expect(err).ToNot(HaveOccurred())
   798  			g.Expect(l.Items).To(HaveLen(1))
   799  		})
   800  	}
   801  }
   802  
   803  // Ensure bootstrap data is generated in the correct format based on the format specified in the
   804  // KubeadmConfig resource.
   805  func TestBootstrapDataFormat(t *testing.T) {
   806  	testcases := []struct {
   807  		name               string
   808  		isWorker           bool
   809  		format             bootstrapv1.Format
   810  		clusterInitialized bool
   811  	}{
   812  		{
   813  			name:   "cloud-config init config",
   814  			format: bootstrapv1.CloudConfig,
   815  		},
   816  		{
   817  			name:   "Ignition init config",
   818  			format: bootstrapv1.Ignition,
   819  		},
   820  		{
   821  			name:               "Ignition control plane join config",
   822  			format:             bootstrapv1.Ignition,
   823  			clusterInitialized: true,
   824  		},
   825  		{
   826  			name:               "Ignition worker join config",
   827  			isWorker:           true,
   828  			format:             bootstrapv1.Ignition,
   829  			clusterInitialized: true,
   830  		},
   831  		{
   832  			name: "Empty format field",
   833  		},
   834  	}
   835  
   836  	for _, tc := range testcases {
   837  		t.Run(tc.name, func(t *testing.T) {
   838  			g := NewWithT(t)
   839  
   840  			cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   841  			cluster.Status.InfrastructureReady = true
   842  			cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
   843  			if tc.clusterInitialized {
   844  				conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   845  			}
   846  
   847  			var machine *clusterv1.Machine
   848  			var config *bootstrapv1.KubeadmConfig
   849  			var configName string
   850  			if tc.isWorker {
   851  				machine = newWorkerMachineForCluster(cluster)
   852  				configName = "worker-join-cfg"
   853  				config = newWorkerJoinKubeadmConfig(metav1.NamespaceDefault, configName)
   854  				addKubeadmConfigToMachine(config, machine)
   855  			} else {
   856  				machine = newControlPlaneMachine(cluster, "machine")
   857  				configName = "cfg"
   858  				config = newControlPlaneInitKubeadmConfig(metav1.NamespaceDefault, configName)
   859  				addKubeadmConfigToMachine(config, machine)
   860  			}
   861  			config.Spec.Format = tc.format
   862  
   863  			objects := []client.Object{
   864  				cluster,
   865  				machine,
   866  				config,
   867  			}
   868  			objects = append(objects, createSecrets(t, cluster, config)...)
   869  
   870  			myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   871  
   872  			k := &KubeadmConfigReconciler{
   873  				Client:              myclient,
   874  				SecretCachingClient: myclient,
   875  				Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
   876  				KubeadmInitLock:     &myInitLocker{},
   877  			}
   878  			request := ctrl.Request{
   879  				NamespacedName: client.ObjectKey{
   880  					Namespace: metav1.NamespaceDefault,
   881  					Name:      configName,
   882  				},
   883  			}
   884  
   885  			// Reconcile the KubeadmConfig resource.
   886  			_, err := k.Reconcile(ctx, request)
   887  			g.Expect(err).ToNot(HaveOccurred())
   888  
   889  			// Verify the KubeadmConfig resource state is correct.
   890  			cfg, err := getKubeadmConfig(myclient, configName, metav1.NamespaceDefault)
   891  			g.Expect(err).ToNot(HaveOccurred())
   892  			g.Expect(cfg.Status.Ready).To(BeTrue())
   893  			g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
   894  
   895  			// Read the secret containing the bootstrap data which was generated by the
   896  			// KubeadmConfig controller.
   897  			key := client.ObjectKey{
   898  				Namespace: metav1.NamespaceDefault,
   899  				Name:      *cfg.Status.DataSecretName,
   900  			}
   901  			secret := &corev1.Secret{}
   902  			err = myclient.Get(ctx, key, secret)
   903  			g.Expect(err).ToNot(HaveOccurred())
   904  
   905  			// Verify the format field of the bootstrap data secret is correct.
   906  			g.Expect(string(secret.Data["format"])).To(Equal(string(tc.format)))
   907  
   908  			// Verify the bootstrap data value is in the correct format.
   909  			data := secret.Data["value"]
   910  			switch tc.format {
   911  			case bootstrapv1.CloudConfig, "":
   912  				// Verify the bootstrap data is valid YAML.
   913  				// TODO: Verify the YAML document is valid cloud-config?
   914  				var out interface{}
   915  				err = yaml.Unmarshal(data, &out)
   916  				g.Expect(err).ToNot(HaveOccurred())
   917  			case bootstrapv1.Ignition:
   918  				// Verify the bootstrap data is valid Ignition.
   919  				_, reports, err := ignition.Parse(data)
   920  				g.Expect(err).ToNot(HaveOccurred())
   921  				g.Expect(reports.IsFatal()).NotTo(BeTrue())
   922  			}
   923  		})
   924  	}
   925  }
   926  
   927  // during kubeadmconfig reconcile it is possible that bootstrap secret gets created
   928  // but kubeadmconfig is not patched, do not error if secret already exists.
   929  // ignore the alreadyexists error and update the status to ready.
   930  func TestKubeadmConfigSecretCreatedStatusNotPatched(t *testing.T) {
   931  	g := NewWithT(t)
   932  
   933  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
   934  	cluster.Status.InfrastructureReady = true
   935  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   936  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
   937  
   938  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
   939  	initConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "control-plane-init-config")
   940  	addKubeadmConfigToMachine(initConfig, controlPlaneInitMachine)
   941  
   942  	workerMachine := newWorkerMachineForCluster(cluster)
   943  	workerJoinConfig := newWorkerJoinKubeadmConfig(metav1.NamespaceDefault, "worker-join-cfg")
   944  	addKubeadmConfigToMachine(workerJoinConfig, workerMachine)
   945  	objects := []client.Object{
   946  		cluster,
   947  		workerMachine,
   948  		workerJoinConfig,
   949  	}
   950  
   951  	objects = append(objects, createSecrets(t, cluster, initConfig)...)
   952  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
   953  	k := &KubeadmConfigReconciler{
   954  		Client:              myclient,
   955  		SecretCachingClient: myclient,
   956  		Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
   957  		KubeadmInitLock:     &myInitLocker{},
   958  	}
   959  	request := ctrl.Request{
   960  		NamespacedName: client.ObjectKey{
   961  			Namespace: metav1.NamespaceDefault,
   962  			Name:      "worker-join-cfg",
   963  		},
   964  	}
   965  	secret := &corev1.Secret{
   966  		ObjectMeta: metav1.ObjectMeta{
   967  			Name:      workerJoinConfig.Name,
   968  			Namespace: workerJoinConfig.Namespace,
   969  			Labels: map[string]string{
   970  				clusterv1.ClusterNameLabel: cluster.Name,
   971  			},
   972  			OwnerReferences: []metav1.OwnerReference{
   973  				{
   974  					APIVersion: bootstrapv1.GroupVersion.String(),
   975  					Kind:       "KubeadmConfig",
   976  					Name:       workerJoinConfig.Name,
   977  					UID:        workerJoinConfig.UID,
   978  					Controller: ptr.To(true),
   979  				},
   980  			},
   981  		},
   982  		Data: map[string][]byte{
   983  			"value": nil,
   984  		},
   985  		Type: clusterv1.ClusterSecretType,
   986  	}
   987  
   988  	err := myclient.Create(ctx, secret)
   989  	g.Expect(err).ToNot(HaveOccurred())
   990  	result, err := k.Reconcile(ctx, request)
   991  	g.Expect(err).ToNot(HaveOccurred())
   992  	g.Expect(result.Requeue).To(BeFalse())
   993  	g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
   994  
   995  	cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault)
   996  	g.Expect(err).ToNot(HaveOccurred())
   997  	g.Expect(cfg.Status.Ready).To(BeTrue())
   998  	g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
   999  	g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
  1000  }
  1001  
  1002  func TestBootstrapTokenTTLExtension(t *testing.T) {
  1003  	g := NewWithT(t)
  1004  
  1005  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
  1006  	cluster.Status.InfrastructureReady = true
  1007  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
  1008  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
  1009  
  1010  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
  1011  	initConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "control-plane-init-config")
  1012  	addKubeadmConfigToMachine(initConfig, controlPlaneInitMachine)
  1013  
  1014  	workerMachine := newWorkerMachineForCluster(cluster)
  1015  	workerJoinConfig := newWorkerJoinKubeadmConfig(metav1.NamespaceDefault, "worker-join-cfg")
  1016  	addKubeadmConfigToMachine(workerJoinConfig, workerMachine)
  1017  
  1018  	controlPlaneJoinMachine := newControlPlaneMachine(cluster, "control-plane-join-machine")
  1019  	controlPlaneJoinConfig := newControlPlaneJoinKubeadmConfig(controlPlaneJoinMachine.Namespace, "control-plane-join-cfg")
  1020  	addKubeadmConfigToMachine(controlPlaneJoinConfig, controlPlaneJoinMachine)
  1021  	objects := []client.Object{
  1022  		cluster,
  1023  		workerMachine,
  1024  		workerJoinConfig,
  1025  		controlPlaneJoinMachine,
  1026  		controlPlaneJoinConfig,
  1027  	}
  1028  
  1029  	objects = append(objects, createSecrets(t, cluster, initConfig)...)
  1030  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}, &clusterv1.Machine{}).Build()
  1031  	remoteClient := fake.NewClientBuilder().Build()
  1032  	k := &KubeadmConfigReconciler{
  1033  		Client:              myclient,
  1034  		SecretCachingClient: myclient,
  1035  		KubeadmInitLock:     &myInitLocker{},
  1036  		TokenTTL:            DefaultTokenTTL,
  1037  		Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, remoteClient, remoteClient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
  1038  	}
  1039  	request := ctrl.Request{
  1040  		NamespacedName: client.ObjectKey{
  1041  			Namespace: metav1.NamespaceDefault,
  1042  			Name:      "worker-join-cfg",
  1043  		},
  1044  	}
  1045  	result, err := k.Reconcile(ctx, request)
  1046  	g.Expect(err).ToNot(HaveOccurred())
  1047  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1048  
  1049  	cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault)
  1050  	g.Expect(err).ToNot(HaveOccurred())
  1051  	g.Expect(cfg.Status.Ready).To(BeTrue())
  1052  	g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
  1053  	g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
  1054  
  1055  	request = ctrl.Request{
  1056  		NamespacedName: client.ObjectKey{
  1057  			Namespace: metav1.NamespaceDefault,
  1058  			Name:      "control-plane-join-cfg",
  1059  		},
  1060  	}
  1061  	result, err = k.Reconcile(ctx, request)
  1062  	g.Expect(err).ToNot(HaveOccurred())
  1063  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1064  
  1065  	cfg, err = getKubeadmConfig(myclient, "control-plane-join-cfg", metav1.NamespaceDefault)
  1066  	g.Expect(err).ToNot(HaveOccurred())
  1067  	g.Expect(cfg.Status.Ready).To(BeTrue())
  1068  	g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
  1069  	g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
  1070  
  1071  	l := &corev1.SecretList{}
  1072  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1073  	g.Expect(l.Items).To(HaveLen(2)) // control plane vs. worker
  1074  
  1075  	t.Log("Ensure that the token secret is not updated while it's still fresh")
  1076  	tokenExpires := make([][]byte, len(l.Items))
  1077  
  1078  	for i, item := range l.Items {
  1079  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1080  	}
  1081  
  1082  	for _, req := range []ctrl.Request{
  1083  		{
  1084  			NamespacedName: client.ObjectKey{
  1085  				Namespace: metav1.NamespaceDefault,
  1086  				Name:      "worker-join-cfg",
  1087  			},
  1088  		},
  1089  		{
  1090  			NamespacedName: client.ObjectKey{
  1091  				Namespace: metav1.NamespaceDefault,
  1092  				Name:      "control-plane-join-cfg",
  1093  			},
  1094  		},
  1095  	} {
  1096  		result, err := k.Reconcile(ctx, req)
  1097  		g.Expect(err).ToNot(HaveOccurred())
  1098  		g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1099  	}
  1100  
  1101  	l = &corev1.SecretList{}
  1102  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1103  	g.Expect(l.Items).To(HaveLen(2))
  1104  
  1105  	for i, item := range l.Items {
  1106  		// No refresh should have happened since no time passed and the token is therefore still fresh
  1107  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeTrue())
  1108  	}
  1109  
  1110  	t.Log("Ensure that the token secret is updated if expiration time is soon")
  1111  
  1112  	for i, item := range l.Items {
  1113  		// Simulate that expiry time is only TTL/2 from now. This should trigger a refresh.
  1114  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL / 2).Format(time.RFC3339))
  1115  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1116  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1117  	}
  1118  
  1119  	for _, req := range []ctrl.Request{
  1120  		{
  1121  			NamespacedName: client.ObjectKey{
  1122  				Namespace: metav1.NamespaceDefault,
  1123  				Name:      "worker-join-cfg",
  1124  			},
  1125  		},
  1126  		{
  1127  			NamespacedName: client.ObjectKey{
  1128  				Namespace: metav1.NamespaceDefault,
  1129  				Name:      "control-plane-join-cfg",
  1130  			},
  1131  		},
  1132  	} {
  1133  		result, err := k.Reconcile(ctx, req)
  1134  		g.Expect(err).ToNot(HaveOccurred())
  1135  		g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1136  	}
  1137  
  1138  	l = &corev1.SecretList{}
  1139  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1140  	g.Expect(l.Items).To(HaveLen(2))
  1141  
  1142  	for i, item := range l.Items {
  1143  		// Refresh should have happened since expiration is soon
  1144  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeFalse())
  1145  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1146  	}
  1147  
  1148  	t.Log("If infrastructure is marked ready, the token should still be refreshed")
  1149  
  1150  	for i, item := range l.Items {
  1151  		// Simulate that expiry time is only TTL/2 from now. This should trigger a refresh.
  1152  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL / 2).Format(time.RFC3339))
  1153  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1154  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1155  	}
  1156  
  1157  	patchHelper, err := patch.NewHelper(workerMachine, myclient)
  1158  	g.Expect(err).ShouldNot(HaveOccurred())
  1159  	workerMachine.Status.InfrastructureReady = true
  1160  	g.Expect(patchHelper.Patch(ctx, workerMachine)).To(Succeed())
  1161  
  1162  	patchHelper, err = patch.NewHelper(controlPlaneJoinMachine, myclient)
  1163  	g.Expect(err).ShouldNot(HaveOccurred())
  1164  	controlPlaneJoinMachine.Status.InfrastructureReady = true
  1165  	g.Expect(patchHelper.Patch(ctx, controlPlaneJoinMachine)).To(Succeed())
  1166  
  1167  	for _, req := range []ctrl.Request{
  1168  		{
  1169  			NamespacedName: client.ObjectKey{
  1170  				Namespace: metav1.NamespaceDefault,
  1171  				Name:      "worker-join-cfg",
  1172  			},
  1173  		},
  1174  		{
  1175  			NamespacedName: client.ObjectKey{
  1176  				Namespace: metav1.NamespaceDefault,
  1177  				Name:      "control-plane-join-cfg",
  1178  			},
  1179  		},
  1180  	} {
  1181  		result, err := k.Reconcile(ctx, req)
  1182  		g.Expect(err).ToNot(HaveOccurred())
  1183  		g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1184  	}
  1185  
  1186  	l = &corev1.SecretList{}
  1187  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1188  	g.Expect(l.Items).To(HaveLen(2))
  1189  
  1190  	for i, item := range l.Items {
  1191  		// Refresh should have happened since expiration is soon, even if infrastructure is ready
  1192  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeFalse())
  1193  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1194  	}
  1195  
  1196  	t.Log("When the Nodes have actually joined the cluster and we get a nodeRef, no more refresh should happen")
  1197  
  1198  	for i, item := range l.Items {
  1199  		// Simulate that expiry time is only TTL/2 from now. This would normally trigger a refresh.
  1200  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL / 2).Format(time.RFC3339))
  1201  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1202  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1203  	}
  1204  
  1205  	patchHelper, err = patch.NewHelper(workerMachine, myclient)
  1206  	g.Expect(err).ShouldNot(HaveOccurred())
  1207  	workerMachine.Status.NodeRef = &corev1.ObjectReference{
  1208  		APIVersion: "v1",
  1209  		Kind:       "Node",
  1210  		Name:       "worker-node",
  1211  	}
  1212  	g.Expect(patchHelper.Patch(ctx, workerMachine)).To(Succeed())
  1213  
  1214  	patchHelper, err = patch.NewHelper(controlPlaneJoinMachine, myclient)
  1215  	g.Expect(err).ShouldNot(HaveOccurred())
  1216  	controlPlaneJoinMachine.Status.NodeRef = &corev1.ObjectReference{
  1217  		APIVersion: "v1",
  1218  		Kind:       "Node",
  1219  		Name:       "control-plane-node",
  1220  	}
  1221  	g.Expect(patchHelper.Patch(ctx, controlPlaneJoinMachine)).To(Succeed())
  1222  
  1223  	for _, req := range []ctrl.Request{
  1224  		{
  1225  			NamespacedName: client.ObjectKey{
  1226  				Namespace: metav1.NamespaceDefault,
  1227  				Name:      "worker-join-cfg",
  1228  			},
  1229  		},
  1230  		{
  1231  			NamespacedName: client.ObjectKey{
  1232  				Namespace: metav1.NamespaceDefault,
  1233  				Name:      "control-plane-join-cfg",
  1234  			},
  1235  		},
  1236  	} {
  1237  		result, err := k.Reconcile(ctx, req)
  1238  		g.Expect(err).ToNot(HaveOccurred())
  1239  		g.Expect(result.Requeue).To(BeFalse())
  1240  		g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
  1241  	}
  1242  
  1243  	l = &corev1.SecretList{}
  1244  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1245  	g.Expect(l.Items).To(HaveLen(2))
  1246  
  1247  	for i, item := range l.Items {
  1248  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeTrue())
  1249  	}
  1250  }
  1251  
  1252  func TestBootstrapTokenRotationMachinePool(t *testing.T) {
  1253  	_ = feature.MutableGates.Set("MachinePool=true")
  1254  	g := NewWithT(t)
  1255  
  1256  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
  1257  	cluster.Status.InfrastructureReady = true
  1258  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
  1259  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{Host: "100.105.150.1", Port: 6443}
  1260  
  1261  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
  1262  	initConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "control-plane-init-config")
  1263  
  1264  	addKubeadmConfigToMachine(initConfig, controlPlaneInitMachine)
  1265  
  1266  	workerMachinePool := newWorkerMachinePoolForCluster(cluster)
  1267  	workerJoinConfig := newWorkerJoinKubeadmConfig(workerMachinePool.Namespace, "workerpool-join-cfg")
  1268  	addKubeadmConfigToMachinePool(workerJoinConfig, workerMachinePool)
  1269  	objects := []client.Object{
  1270  		cluster,
  1271  		workerMachinePool,
  1272  		workerJoinConfig,
  1273  	}
  1274  
  1275  	objects = append(objects, createSecrets(t, cluster, initConfig)...)
  1276  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}, &expv1.MachinePool{}).Build()
  1277  	remoteClient := fake.NewClientBuilder().Build()
  1278  	k := &KubeadmConfigReconciler{
  1279  		Client:              myclient,
  1280  		SecretCachingClient: myclient,
  1281  		KubeadmInitLock:     &myInitLocker{},
  1282  		TokenTTL:            DefaultTokenTTL,
  1283  		Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, remoteClient, remoteClient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
  1284  	}
  1285  	request := ctrl.Request{
  1286  		NamespacedName: client.ObjectKey{
  1287  			Namespace: metav1.NamespaceDefault,
  1288  			Name:      "workerpool-join-cfg",
  1289  		},
  1290  	}
  1291  	result, err := k.Reconcile(ctx, request)
  1292  	g.Expect(err).ToNot(HaveOccurred())
  1293  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1294  
  1295  	cfg, err := getKubeadmConfig(myclient, "workerpool-join-cfg", metav1.NamespaceDefault)
  1296  	g.Expect(err).ToNot(HaveOccurred())
  1297  	g.Expect(cfg.Status.Ready).To(BeTrue())
  1298  	g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
  1299  	g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
  1300  
  1301  	l := &corev1.SecretList{}
  1302  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1303  	g.Expect(l.Items).To(HaveLen(1))
  1304  
  1305  	t.Log("Ensure that the token secret is not updated while it's still fresh")
  1306  	tokenExpires := make([][]byte, len(l.Items))
  1307  
  1308  	for i, item := range l.Items {
  1309  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1310  	}
  1311  
  1312  	result, err = k.Reconcile(ctx, request)
  1313  	g.Expect(err).ToNot(HaveOccurred())
  1314  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1315  
  1316  	l = &corev1.SecretList{}
  1317  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1318  	g.Expect(l.Items).To(HaveLen(1))
  1319  
  1320  	for i, item := range l.Items {
  1321  		// No refresh should have happened since no time passed and the token is therefore still fresh
  1322  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeTrue())
  1323  	}
  1324  
  1325  	t.Log("Ensure that the token secret is updated if expiration time is soon")
  1326  
  1327  	for i, item := range l.Items {
  1328  		// Simulate that expiry time is only TTL*3/4 from now. This should trigger a refresh.
  1329  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL * 3 / 4).Format(time.RFC3339))
  1330  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1331  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1332  	}
  1333  
  1334  	result, err = k.Reconcile(ctx, request)
  1335  	g.Expect(err).ToNot(HaveOccurred())
  1336  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1337  
  1338  	l = &corev1.SecretList{}
  1339  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1340  	g.Expect(l.Items).To(HaveLen(1))
  1341  
  1342  	for i, item := range l.Items {
  1343  		// Refresh should have happened since expiration is soon
  1344  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeFalse())
  1345  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1346  	}
  1347  
  1348  	t.Log("If infrastructure is marked ready, the token should still be refreshed")
  1349  
  1350  	for i, item := range l.Items {
  1351  		// Simulate that expiry time is only TTL*3/4 from now. This should trigger a refresh.
  1352  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL * 3 / 4).Format(time.RFC3339))
  1353  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1354  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1355  	}
  1356  
  1357  	patchHelper, err := patch.NewHelper(workerMachinePool, myclient)
  1358  	g.Expect(err).ShouldNot(HaveOccurred())
  1359  	workerMachinePool.Status.InfrastructureReady = true
  1360  	g.Expect(patchHelper.Patch(ctx, workerMachinePool, patch.WithStatusObservedGeneration{})).To(Succeed())
  1361  
  1362  	result, err = k.Reconcile(ctx, request)
  1363  	g.Expect(err).ToNot(HaveOccurred())
  1364  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1365  
  1366  	l = &corev1.SecretList{}
  1367  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1368  	g.Expect(l.Items).To(HaveLen(1))
  1369  
  1370  	for i, item := range l.Items {
  1371  		// Refresh should have happened since expiration is soon, even if infrastructure is ready
  1372  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeFalse())
  1373  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1374  	}
  1375  
  1376  	t.Log("When the Nodes have actually joined the cluster and we get a nodeRef, no more refresh should happen")
  1377  
  1378  	for i, item := range l.Items {
  1379  		// Simulate that expiry time is only TTL*3/4 from now. This would normally trigger a refresh.
  1380  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL * 3 / 4).Format(time.RFC3339))
  1381  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1382  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1383  	}
  1384  
  1385  	workerMachinePool.Status.NodeRefs = []corev1.ObjectReference{
  1386  		{
  1387  			Kind:      "Node",
  1388  			Namespace: metav1.NamespaceDefault,
  1389  			Name:      "node-0",
  1390  		},
  1391  	}
  1392  	g.Expect(patchHelper.Patch(ctx, workerMachinePool, patch.WithStatusObservedGeneration{})).To(Succeed())
  1393  
  1394  	result, err = k.Reconcile(ctx, request)
  1395  	g.Expect(err).ToNot(HaveOccurred())
  1396  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1397  
  1398  	l = &corev1.SecretList{}
  1399  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1400  	g.Expect(l.Items).To(HaveLen(1))
  1401  
  1402  	for i, item := range l.Items {
  1403  		g.Expect(bytes.Equal(tokenExpires[i], item.Data[bootstrapapi.BootstrapTokenExpirationKey])).To(BeTrue())
  1404  	}
  1405  
  1406  	t.Log("Token must be rotated before it expires")
  1407  
  1408  	for i, item := range l.Items {
  1409  		// Simulate that expiry time is only TTL*4/10 from now. This should trigger rotation.
  1410  		item.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(k.TokenTTL * 4 / 10).Format(time.RFC3339))
  1411  		g.Expect(remoteClient.Update(ctx, &l.Items[i])).To(Succeed())
  1412  		tokenExpires[i] = item.Data[bootstrapapi.BootstrapTokenExpirationKey]
  1413  	}
  1414  
  1415  	request = ctrl.Request{
  1416  		NamespacedName: client.ObjectKey{
  1417  			Namespace: metav1.NamespaceDefault,
  1418  			Name:      "workerpool-join-cfg",
  1419  		},
  1420  	}
  1421  	result, err = k.Reconcile(ctx, request)
  1422  	g.Expect(err).ToNot(HaveOccurred())
  1423  	g.Expect(result.RequeueAfter).To(Equal(k.TokenTTL / 3))
  1424  
  1425  	l = &corev1.SecretList{}
  1426  	g.Expect(remoteClient.List(ctx, l, client.ListOption(client.InNamespace(metav1.NamespaceSystem)))).To(Succeed())
  1427  	g.Expect(l.Items).To(HaveLen(2)) // old and new token
  1428  	foundOld := false
  1429  	foundNew := true
  1430  	for _, item := range l.Items {
  1431  		if bytes.Equal(item.Data[bootstrapapi.BootstrapTokenExpirationKey], tokenExpires[0]) {
  1432  			foundOld = true
  1433  		} else {
  1434  			expirationTime, err := time.Parse(time.RFC3339, string(item.Data[bootstrapapi.BootstrapTokenExpirationKey]))
  1435  			g.Expect(err).ToNot(HaveOccurred())
  1436  			g.Expect(expirationTime).Should(BeTemporally("~", time.Now().UTC().Add(k.TokenTTL), 10*time.Second))
  1437  			foundNew = true
  1438  		}
  1439  	}
  1440  	g.Expect(foundOld).To(BeTrue())
  1441  	g.Expect(foundNew).To(BeTrue())
  1442  }
  1443  
  1444  // Ensure the discovery portion of the JoinConfiguration gets generated correctly.
  1445  func TestKubeadmConfigReconciler_Reconcile_DiscoveryReconcileBehaviors(t *testing.T) {
  1446  	caHash := []string{"...."}
  1447  	bootstrapToken := bootstrapv1.Discovery{
  1448  		BootstrapToken: &bootstrapv1.BootstrapTokenDiscovery{
  1449  			CACertHashes: caHash,
  1450  		},
  1451  	}
  1452  	goodcluster := &clusterv1.Cluster{
  1453  		Spec: clusterv1.ClusterSpec{
  1454  			ControlPlaneEndpoint: clusterv1.APIEndpoint{
  1455  				Host: "example.com",
  1456  				Port: 6443,
  1457  			},
  1458  		},
  1459  	}
  1460  	testcases := []struct {
  1461  		name              string
  1462  		cluster           *clusterv1.Cluster
  1463  		config            *bootstrapv1.KubeadmConfig
  1464  		validateDiscovery func(*WithT, *bootstrapv1.KubeadmConfig) error
  1465  	}{
  1466  		{
  1467  			name:    "Automatically generate token if discovery not specified",
  1468  			cluster: goodcluster,
  1469  			config: &bootstrapv1.KubeadmConfig{
  1470  				Spec: bootstrapv1.KubeadmConfigSpec{
  1471  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
  1472  						Discovery: bootstrapToken,
  1473  					},
  1474  				},
  1475  			},
  1476  			validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error {
  1477  				d := c.Spec.JoinConfiguration.Discovery
  1478  				g.Expect(d.BootstrapToken).NotTo(BeNil())
  1479  				g.Expect(d.BootstrapToken.Token).NotTo(Equal(""))
  1480  				g.Expect(d.BootstrapToken.APIServerEndpoint).To(Equal("example.com:6443"))
  1481  				g.Expect(d.BootstrapToken.UnsafeSkipCAVerification).To(BeFalse())
  1482  				return nil
  1483  			},
  1484  		},
  1485  		{
  1486  			name:    "Respect discoveryConfiguration.File",
  1487  			cluster: goodcluster,
  1488  			config: &bootstrapv1.KubeadmConfig{
  1489  				Spec: bootstrapv1.KubeadmConfigSpec{
  1490  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
  1491  						Discovery: bootstrapv1.Discovery{
  1492  							File: &bootstrapv1.FileDiscovery{},
  1493  						},
  1494  					},
  1495  				},
  1496  			},
  1497  			validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error {
  1498  				d := c.Spec.JoinConfiguration.Discovery
  1499  				g.Expect(d.BootstrapToken).To(BeNil())
  1500  				return nil
  1501  			},
  1502  		},
  1503  		{
  1504  			name:    "Respect discoveryConfiguration.BootstrapToken.APIServerEndpoint",
  1505  			cluster: goodcluster,
  1506  			config: &bootstrapv1.KubeadmConfig{
  1507  				Spec: bootstrapv1.KubeadmConfigSpec{
  1508  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
  1509  						Discovery: bootstrapv1.Discovery{
  1510  							BootstrapToken: &bootstrapv1.BootstrapTokenDiscovery{
  1511  								CACertHashes:      caHash,
  1512  								APIServerEndpoint: "bar.com:6443",
  1513  							},
  1514  						},
  1515  					},
  1516  				},
  1517  			},
  1518  			validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error {
  1519  				d := c.Spec.JoinConfiguration.Discovery
  1520  				g.Expect(d.BootstrapToken.APIServerEndpoint).To(Equal("bar.com:6443"))
  1521  				return nil
  1522  			},
  1523  		},
  1524  		{
  1525  			name:    "Respect discoveryConfiguration.BootstrapToken.Token",
  1526  			cluster: goodcluster,
  1527  			config: &bootstrapv1.KubeadmConfig{
  1528  				Spec: bootstrapv1.KubeadmConfigSpec{
  1529  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
  1530  						Discovery: bootstrapv1.Discovery{
  1531  							BootstrapToken: &bootstrapv1.BootstrapTokenDiscovery{
  1532  								CACertHashes: caHash,
  1533  								Token:        "abcdef.0123456789abcdef",
  1534  							},
  1535  						},
  1536  					},
  1537  				},
  1538  			},
  1539  			validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error {
  1540  				d := c.Spec.JoinConfiguration.Discovery
  1541  				g.Expect(d.BootstrapToken.Token).To(Equal("abcdef.0123456789abcdef"))
  1542  				return nil
  1543  			},
  1544  		},
  1545  		{
  1546  			name:    "Respect discoveryConfiguration.BootstrapToken.CACertHashes",
  1547  			cluster: goodcluster,
  1548  			config: &bootstrapv1.KubeadmConfig{
  1549  				Spec: bootstrapv1.KubeadmConfigSpec{
  1550  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
  1551  						Discovery: bootstrapv1.Discovery{
  1552  							BootstrapToken: &bootstrapv1.BootstrapTokenDiscovery{
  1553  								CACertHashes: caHash,
  1554  							},
  1555  						},
  1556  					},
  1557  				},
  1558  			},
  1559  			validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error {
  1560  				d := c.Spec.JoinConfiguration.Discovery
  1561  				g.Expect(reflect.DeepEqual(d.BootstrapToken.CACertHashes, caHash)).To(BeTrue())
  1562  				return nil
  1563  			},
  1564  		},
  1565  	}
  1566  
  1567  	for _, tc := range testcases {
  1568  		t.Run(tc.name, func(t *testing.T) {
  1569  			g := NewWithT(t)
  1570  
  1571  			fakeClient := fake.NewClientBuilder().Build()
  1572  			k := &KubeadmConfigReconciler{
  1573  				Client:              fakeClient,
  1574  				SecretCachingClient: fakeClient,
  1575  				Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: tc.cluster.Name, Namespace: tc.cluster.Namespace}),
  1576  				KubeadmInitLock:     &myInitLocker{},
  1577  			}
  1578  
  1579  			res, err := k.reconcileDiscovery(ctx, tc.cluster, tc.config, secret.Certificates{})
  1580  			g.Expect(res.IsZero()).To(BeTrue())
  1581  			g.Expect(err).ToNot(HaveOccurred())
  1582  
  1583  			err = tc.validateDiscovery(g, tc.config)
  1584  			g.Expect(err).ToNot(HaveOccurred())
  1585  		})
  1586  	}
  1587  }
  1588  
  1589  // Test failure cases for the discovery reconcile function.
  1590  func TestKubeadmConfigReconciler_Reconcile_DiscoveryReconcileFailureBehaviors(t *testing.T) {
  1591  	k := &KubeadmConfigReconciler{}
  1592  
  1593  	testcases := []struct {
  1594  		name    string
  1595  		cluster *clusterv1.Cluster
  1596  		config  *bootstrapv1.KubeadmConfig
  1597  
  1598  		result ctrl.Result
  1599  		err    error
  1600  	}{
  1601  		{
  1602  			name:    "Should requeue if cluster has not ControlPlaneEndpoint",
  1603  			cluster: &clusterv1.Cluster{}, // cluster without endpoints
  1604  			config: &bootstrapv1.KubeadmConfig{
  1605  				Spec: bootstrapv1.KubeadmConfigSpec{
  1606  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
  1607  						Discovery: bootstrapv1.Discovery{
  1608  							BootstrapToken: &bootstrapv1.BootstrapTokenDiscovery{
  1609  								CACertHashes: []string{"item"},
  1610  							},
  1611  						},
  1612  					},
  1613  				},
  1614  			},
  1615  			result: ctrl.Result{RequeueAfter: 10 * time.Second},
  1616  		},
  1617  	}
  1618  
  1619  	for _, tc := range testcases {
  1620  		t.Run(tc.name, func(t *testing.T) {
  1621  			g := NewWithT(t)
  1622  
  1623  			res, err := k.reconcileDiscovery(ctx, tc.cluster, tc.config, secret.Certificates{})
  1624  			g.Expect(res).To(BeComparableTo(tc.result))
  1625  			if tc.err == nil {
  1626  				g.Expect(err).ToNot(HaveOccurred())
  1627  			} else {
  1628  				g.Expect(err).To(Equal(tc.err))
  1629  			}
  1630  		})
  1631  	}
  1632  }
  1633  
  1634  // Set cluster configuration defaults based on dynamic values from the cluster object.
  1635  func TestKubeadmConfigReconciler_Reconcile_DynamicDefaultsForClusterConfiguration(t *testing.T) {
  1636  	k := &KubeadmConfigReconciler{}
  1637  
  1638  	testcases := []struct {
  1639  		name    string
  1640  		cluster *clusterv1.Cluster
  1641  		machine *clusterv1.Machine
  1642  		config  *bootstrapv1.KubeadmConfig
  1643  	}{
  1644  		{
  1645  			name: "Config settings have precedence",
  1646  			config: &bootstrapv1.KubeadmConfig{
  1647  				Spec: bootstrapv1.KubeadmConfigSpec{
  1648  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
  1649  						ClusterName:       "mycluster",
  1650  						KubernetesVersion: "myversion",
  1651  						Networking: bootstrapv1.Networking{
  1652  							PodSubnet:     "myPodSubnet",
  1653  							ServiceSubnet: "myServiceSubnet",
  1654  							DNSDomain:     "myDNSDomain",
  1655  						},
  1656  						ControlPlaneEndpoint: "myControlPlaneEndpoint:6443",
  1657  					},
  1658  				},
  1659  			},
  1660  			cluster: &clusterv1.Cluster{
  1661  				ObjectMeta: metav1.ObjectMeta{
  1662  					Name: "OtherName",
  1663  				},
  1664  				Spec: clusterv1.ClusterSpec{
  1665  					ClusterNetwork: &clusterv1.ClusterNetwork{
  1666  						Services:      &clusterv1.NetworkRanges{CIDRBlocks: []string{"otherServicesCidr"}},
  1667  						Pods:          &clusterv1.NetworkRanges{CIDRBlocks: []string{"otherPodsCidr"}},
  1668  						ServiceDomain: "otherServiceDomain",
  1669  					},
  1670  					ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "otherVersion", Port: 0},
  1671  				},
  1672  			},
  1673  			machine: &clusterv1.Machine{
  1674  				Spec: clusterv1.MachineSpec{
  1675  					Version: ptr.To("otherVersion"),
  1676  				},
  1677  			},
  1678  		},
  1679  		{
  1680  			name: "Top level object settings are used in case config settings are missing",
  1681  			config: &bootstrapv1.KubeadmConfig{
  1682  				Spec: bootstrapv1.KubeadmConfigSpec{
  1683  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
  1684  				},
  1685  			},
  1686  			cluster: &clusterv1.Cluster{
  1687  				ObjectMeta: metav1.ObjectMeta{
  1688  					Name: "mycluster",
  1689  				},
  1690  				Spec: clusterv1.ClusterSpec{
  1691  					ClusterNetwork: &clusterv1.ClusterNetwork{
  1692  						Services:      &clusterv1.NetworkRanges{CIDRBlocks: []string{"myServiceSubnet"}},
  1693  						Pods:          &clusterv1.NetworkRanges{CIDRBlocks: []string{"myPodSubnet"}},
  1694  						ServiceDomain: "myDNSDomain",
  1695  					},
  1696  					ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "myControlPlaneEndpoint", Port: 6443},
  1697  				},
  1698  			},
  1699  			machine: &clusterv1.Machine{
  1700  				Spec: clusterv1.MachineSpec{
  1701  					Version: ptr.To("myversion"),
  1702  				},
  1703  			},
  1704  		},
  1705  	}
  1706  
  1707  	for _, tc := range testcases {
  1708  		t.Run(tc.name, func(t *testing.T) {
  1709  			g := NewWithT(t)
  1710  
  1711  			k.reconcileTopLevelObjectSettings(ctx, tc.cluster, tc.machine, tc.config)
  1712  
  1713  			g.Expect(tc.config.Spec.ClusterConfiguration.ControlPlaneEndpoint).To(Equal("myControlPlaneEndpoint:6443"))
  1714  			g.Expect(tc.config.Spec.ClusterConfiguration.ClusterName).To(Equal("mycluster"))
  1715  			g.Expect(tc.config.Spec.ClusterConfiguration.Networking.PodSubnet).To(Equal("myPodSubnet"))
  1716  			g.Expect(tc.config.Spec.ClusterConfiguration.Networking.ServiceSubnet).To(Equal("myServiceSubnet"))
  1717  			g.Expect(tc.config.Spec.ClusterConfiguration.Networking.DNSDomain).To(Equal("myDNSDomain"))
  1718  			g.Expect(tc.config.Spec.ClusterConfiguration.KubernetesVersion).To(Equal("myversion"))
  1719  		})
  1720  	}
  1721  }
  1722  
  1723  // Allow users to skip CA Verification if they *really* want to.
  1724  func TestKubeadmConfigReconciler_Reconcile_AlwaysCheckCAVerificationUnlessRequestedToSkip(t *testing.T) {
  1725  	// Setup work for an initialized cluster
  1726  	clusterName := "my-cluster"
  1727  	cluster := builder.Cluster(metav1.NamespaceDefault, clusterName).Build()
  1728  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
  1729  	cluster.Status.InfrastructureReady = true
  1730  	cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
  1731  		Host: "example.com",
  1732  		Port: 6443,
  1733  	}
  1734  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "my-control-plane-init-machine")
  1735  	initConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "my-control-plane-init-config")
  1736  
  1737  	controlPlaneMachineName := "my-machine"
  1738  	machine := builder.Machine(metav1.NamespaceDefault, controlPlaneMachineName).
  1739  		WithVersion("v1.19.1").
  1740  		WithClusterName(cluster.Name).
  1741  		Build()
  1742  
  1743  	workerMachineName := "my-worker"
  1744  	workerMachine := builder.Machine(metav1.NamespaceDefault, workerMachineName).
  1745  		WithVersion("v1.19.1").
  1746  		WithClusterName(cluster.Name).
  1747  		Build()
  1748  
  1749  	controlPlaneConfigName := "my-config"
  1750  	config := newKubeadmConfig(metav1.NamespaceDefault, controlPlaneConfigName)
  1751  
  1752  	objects := []client.Object{
  1753  		cluster, machine, workerMachine, config,
  1754  	}
  1755  	objects = append(objects, createSecrets(t, cluster, initConfig)...)
  1756  
  1757  	testcases := []struct {
  1758  		name               string
  1759  		discovery          *bootstrapv1.BootstrapTokenDiscovery
  1760  		skipCAVerification bool
  1761  	}{
  1762  		{
  1763  			name:               "Do not skip CA verification by default",
  1764  			discovery:          &bootstrapv1.BootstrapTokenDiscovery{},
  1765  			skipCAVerification: false,
  1766  		},
  1767  		{
  1768  			name: "Skip CA verification if requested by the user",
  1769  			discovery: &bootstrapv1.BootstrapTokenDiscovery{
  1770  				UnsafeSkipCAVerification: true,
  1771  			},
  1772  			skipCAVerification: true,
  1773  		},
  1774  		{
  1775  			// skipCAVerification should be true since no Cert Hashes are provided, but reconcile will *always* get or create certs.
  1776  			// TODO: Certificate get/create behavior needs to be mocked to enable this test.
  1777  			name: "cannot test for defaulting behavior through the reconcile function",
  1778  			discovery: &bootstrapv1.BootstrapTokenDiscovery{
  1779  				CACertHashes: []string{""},
  1780  			},
  1781  			skipCAVerification: false,
  1782  		},
  1783  	}
  1784  	for _, tc := range testcases {
  1785  		t.Run(tc.name, func(t *testing.T) {
  1786  			g := NewWithT(t)
  1787  
  1788  			myclient := fake.NewClientBuilder().WithObjects(objects...).Build()
  1789  			reconciler := KubeadmConfigReconciler{
  1790  				Client:              myclient,
  1791  				SecretCachingClient: myclient,
  1792  				Tracker:             remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), myclient, myclient, myclient.Scheme(), client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
  1793  				KubeadmInitLock:     &myInitLocker{},
  1794  			}
  1795  
  1796  			wc := newWorkerJoinKubeadmConfig(metav1.NamespaceDefault, "worker-join-cfg")
  1797  			wc.Spec.JoinConfiguration.Discovery.BootstrapToken = tc.discovery
  1798  			key := client.ObjectKey{Namespace: wc.Namespace, Name: wc.Name}
  1799  			err := myclient.Create(ctx, wc)
  1800  			g.Expect(err).ToNot(HaveOccurred())
  1801  
  1802  			req := ctrl.Request{NamespacedName: key}
  1803  			_, err = reconciler.Reconcile(ctx, req)
  1804  			g.Expect(err).ToNot(HaveOccurred())
  1805  
  1806  			cfg := &bootstrapv1.KubeadmConfig{}
  1807  			err = myclient.Get(ctx, key, cfg)
  1808  			g.Expect(err).ToNot(HaveOccurred())
  1809  			g.Expect(cfg.Spec.JoinConfiguration.Discovery.BootstrapToken.UnsafeSkipCAVerification).To(Equal(tc.skipCAVerification))
  1810  		})
  1811  	}
  1812  }
  1813  
  1814  // If a cluster object changes then all associated KubeadmConfigs should be re-reconciled.
  1815  // This allows us to not requeue a kubeadm config while we wait for InfrastructureReady.
  1816  func TestKubeadmConfigReconciler_ClusterToKubeadmConfigs(t *testing.T) {
  1817  	_ = feature.MutableGates.Set("MachinePool=true")
  1818  	g := NewWithT(t)
  1819  
  1820  	cluster := builder.Cluster(metav1.NamespaceDefault, "my-cluster").Build()
  1821  	objs := []client.Object{cluster}
  1822  	expectedNames := []string{}
  1823  	for i := 0; i < 3; i++ {
  1824  		configName := fmt.Sprintf("my-config-%d", i)
  1825  		m := builder.Machine(metav1.NamespaceDefault, fmt.Sprintf("my-machine-%d", i)).
  1826  			WithVersion("v1.19.1").
  1827  			WithClusterName(cluster.Name).
  1828  			WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, configName).Unstructured()).
  1829  			Build()
  1830  		c := newKubeadmConfig(metav1.NamespaceDefault, configName)
  1831  		addKubeadmConfigToMachine(c, m)
  1832  		expectedNames = append(expectedNames, configName)
  1833  		objs = append(objs, m, c)
  1834  	}
  1835  	for i := 3; i < 6; i++ {
  1836  		mp := newMachinePool(cluster, fmt.Sprintf("my-machinepool-%d", i))
  1837  		configName := fmt.Sprintf("my-config-%d", i)
  1838  		c := newKubeadmConfig(mp.Namespace, configName)
  1839  		addKubeadmConfigToMachinePool(c, mp)
  1840  		expectedNames = append(expectedNames, configName)
  1841  		objs = append(objs, mp, c)
  1842  	}
  1843  	fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build()
  1844  	reconciler := &KubeadmConfigReconciler{
  1845  		Client:              fakeClient,
  1846  		SecretCachingClient: fakeClient,
  1847  	}
  1848  	configs := reconciler.ClusterToKubeadmConfigs(ctx, cluster)
  1849  	names := make([]string, 6)
  1850  	for i := range configs {
  1851  		names[i] = configs[i].Name
  1852  	}
  1853  	for _, name := range expectedNames {
  1854  		found := false
  1855  		for _, foundName := range names {
  1856  			if foundName == name {
  1857  				found = true
  1858  			}
  1859  		}
  1860  		g.Expect(found).To(BeTrue())
  1861  	}
  1862  }
  1863  
  1864  // Reconcile should not fail if the Etcd CA Secret already exists.
  1865  func TestKubeadmConfigReconciler_Reconcile_DoesNotFailIfCASecretsAlreadyExist(t *testing.T) {
  1866  	g := NewWithT(t)
  1867  
  1868  	cluster := builder.Cluster(metav1.NamespaceDefault, "my-cluster").Build()
  1869  	cluster.Status.InfrastructureReady = true
  1870  	m := newControlPlaneMachine(cluster, "control-plane-machine")
  1871  	configName := "my-config"
  1872  	c := newControlPlaneInitKubeadmConfig(m.Namespace, configName)
  1873  	scrt := &corev1.Secret{
  1874  		ObjectMeta: metav1.ObjectMeta{
  1875  			Name:      fmt.Sprintf("%s-%s", cluster.Name, secret.EtcdCA),
  1876  			Namespace: metav1.NamespaceDefault,
  1877  		},
  1878  		Data: map[string][]byte{
  1879  			"tls.crt": []byte("hello world"),
  1880  			"tls.key": []byte("hello world"),
  1881  		},
  1882  	}
  1883  	fakec := fake.NewClientBuilder().WithObjects(cluster, m, c, scrt).Build()
  1884  	reconciler := &KubeadmConfigReconciler{
  1885  		Client:              fakec,
  1886  		SecretCachingClient: fakec,
  1887  		KubeadmInitLock:     &myInitLocker{},
  1888  	}
  1889  	req := ctrl.Request{
  1890  		NamespacedName: client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: configName},
  1891  	}
  1892  	_, err := reconciler.Reconcile(ctx, req)
  1893  	g.Expect(err).ToNot(HaveOccurred())
  1894  }
  1895  
  1896  // Exactly one control plane machine initializes if there are multiple control plane machines defined.
  1897  func TestKubeadmConfigReconciler_Reconcile_ExactlyOneControlPlaneMachineInitializes(t *testing.T) {
  1898  	g := NewWithT(t)
  1899  
  1900  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
  1901  	cluster.Status.InfrastructureReady = true
  1902  
  1903  	controlPlaneInitMachineFirst := newControlPlaneMachine(cluster, "control-plane-init-machine-first")
  1904  	controlPlaneInitConfigFirst := newControlPlaneInitKubeadmConfig(controlPlaneInitMachineFirst.Namespace, "control-plane-init-cfg-first")
  1905  	addKubeadmConfigToMachine(controlPlaneInitConfigFirst, controlPlaneInitMachineFirst)
  1906  
  1907  	controlPlaneInitMachineSecond := newControlPlaneMachine(cluster, "control-plane-init-machine-second")
  1908  	controlPlaneInitConfigSecond := newControlPlaneInitKubeadmConfig(controlPlaneInitMachineSecond.Namespace, "control-plane-init-cfg-second")
  1909  	addKubeadmConfigToMachine(controlPlaneInitConfigSecond, controlPlaneInitMachineSecond)
  1910  
  1911  	objects := []client.Object{
  1912  		cluster,
  1913  		controlPlaneInitMachineFirst,
  1914  		controlPlaneInitConfigFirst,
  1915  		controlPlaneInitMachineSecond,
  1916  		controlPlaneInitConfigSecond,
  1917  	}
  1918  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
  1919  	k := &KubeadmConfigReconciler{
  1920  		Client:              myclient,
  1921  		SecretCachingClient: myclient,
  1922  		KubeadmInitLock:     &myInitLocker{},
  1923  	}
  1924  
  1925  	request := ctrl.Request{
  1926  		NamespacedName: client.ObjectKey{
  1927  			Namespace: metav1.NamespaceDefault,
  1928  			Name:      "control-plane-init-cfg-first",
  1929  		},
  1930  	}
  1931  	result, err := k.Reconcile(ctx, request)
  1932  	g.Expect(err).ToNot(HaveOccurred())
  1933  	g.Expect(result.Requeue).To(BeFalse())
  1934  	g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
  1935  
  1936  	request = ctrl.Request{
  1937  		NamespacedName: client.ObjectKey{
  1938  			Namespace: metav1.NamespaceDefault,
  1939  			Name:      "control-plane-init-cfg-second",
  1940  		},
  1941  	}
  1942  	result, err = k.Reconcile(ctx, request)
  1943  	g.Expect(err).ToNot(HaveOccurred())
  1944  	g.Expect(result.Requeue).To(BeFalse())
  1945  	g.Expect(result.RequeueAfter).To(Equal(30 * time.Second))
  1946  	confList := &bootstrapv1.KubeadmConfigList{}
  1947  	g.Expect(myclient.List(ctx, confList)).To(Succeed())
  1948  	for _, c := range confList.Items {
  1949  		// Ensure the DataSecretName is only set for controlPlaneInitConfigFirst.
  1950  		if c.Name == controlPlaneInitConfigFirst.Name {
  1951  			g.Expect(*c.Status.DataSecretName).To(Not(BeEmpty()))
  1952  		}
  1953  		if c.Name == controlPlaneInitConfigSecond.Name {
  1954  			g.Expect(c.Status.DataSecretName).To(BeNil())
  1955  		}
  1956  	}
  1957  }
  1958  
  1959  // Patch should be applied if there is an error in reconcile.
  1960  func TestKubeadmConfigReconciler_Reconcile_PatchWhenErrorOccurred(t *testing.T) {
  1961  	g := NewWithT(t)
  1962  
  1963  	cluster := builder.Cluster(metav1.NamespaceDefault, "cluster").Build()
  1964  	cluster.Status.InfrastructureReady = true
  1965  
  1966  	controlPlaneInitMachine := newControlPlaneMachine(cluster, "control-plane-init-machine")
  1967  	controlPlaneInitConfig := newControlPlaneInitKubeadmConfig(controlPlaneInitMachine.Namespace, "control-plane-init-cfg")
  1968  	addKubeadmConfigToMachine(controlPlaneInitConfig, controlPlaneInitMachine)
  1969  	// set InitConfiguration as nil, we will check this to determine if the kubeadm config has been patched
  1970  	controlPlaneInitConfig.Spec.InitConfiguration = nil
  1971  
  1972  	objects := []client.Object{
  1973  		cluster,
  1974  		controlPlaneInitMachine,
  1975  		controlPlaneInitConfig,
  1976  	}
  1977  
  1978  	secrets := createSecrets(t, cluster, controlPlaneInitConfig)
  1979  	for _, obj := range secrets {
  1980  		s := obj.(*corev1.Secret)
  1981  		delete(s.Data, secret.TLSCrtDataName) // destroy the secrets, which will cause Reconcile to fail
  1982  		objects = append(objects, s)
  1983  	}
  1984  
  1985  	myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
  1986  	k := &KubeadmConfigReconciler{
  1987  		Client:              myclient,
  1988  		SecretCachingClient: myclient,
  1989  		KubeadmInitLock:     &myInitLocker{},
  1990  	}
  1991  
  1992  	request := ctrl.Request{
  1993  		NamespacedName: client.ObjectKey{
  1994  			Namespace: metav1.NamespaceDefault,
  1995  			Name:      "control-plane-init-cfg",
  1996  		},
  1997  	}
  1998  
  1999  	result, err := k.Reconcile(ctx, request)
  2000  	g.Expect(err).To(HaveOccurred())
  2001  	g.Expect(result.Requeue).To(BeFalse())
  2002  	g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
  2003  
  2004  	cfg, err := getKubeadmConfig(myclient, "control-plane-init-cfg", metav1.NamespaceDefault)
  2005  	g.Expect(err).ToNot(HaveOccurred())
  2006  	// check if the kubeadm config has been patched
  2007  	g.Expect(cfg.Spec.InitConfiguration).ToNot(BeNil())
  2008  	g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
  2009  }
  2010  
  2011  func TestKubeadmConfigReconciler_ResolveFiles(t *testing.T) {
  2012  	testSecret := &corev1.Secret{
  2013  		ObjectMeta: metav1.ObjectMeta{
  2014  			Name: "source",
  2015  		},
  2016  		Data: map[string][]byte{
  2017  			"key": []byte("foo"),
  2018  		},
  2019  	}
  2020  
  2021  	cases := map[string]struct {
  2022  		cfg     *bootstrapv1.KubeadmConfig
  2023  		objects []client.Object
  2024  		expect  []bootstrapv1.File
  2025  	}{
  2026  		"content should pass through": {
  2027  			cfg: &bootstrapv1.KubeadmConfig{
  2028  				Spec: bootstrapv1.KubeadmConfigSpec{
  2029  					Files: []bootstrapv1.File{
  2030  						{
  2031  							Content:     "foo",
  2032  							Path:        "/path",
  2033  							Owner:       "root:root",
  2034  							Permissions: "0600",
  2035  						},
  2036  					},
  2037  				},
  2038  			},
  2039  			expect: []bootstrapv1.File{
  2040  				{
  2041  					Content:     "foo",
  2042  					Path:        "/path",
  2043  					Owner:       "root:root",
  2044  					Permissions: "0600",
  2045  				},
  2046  			},
  2047  		},
  2048  		"contentFrom should convert correctly": {
  2049  			cfg: &bootstrapv1.KubeadmConfig{
  2050  				Spec: bootstrapv1.KubeadmConfigSpec{
  2051  					Files: []bootstrapv1.File{
  2052  						{
  2053  							ContentFrom: &bootstrapv1.FileSource{
  2054  								Secret: bootstrapv1.SecretFileSource{
  2055  									Name: "source",
  2056  									Key:  "key",
  2057  								},
  2058  							},
  2059  							Path:        "/path",
  2060  							Owner:       "root:root",
  2061  							Permissions: "0600",
  2062  						},
  2063  					},
  2064  				},
  2065  			},
  2066  			expect: []bootstrapv1.File{
  2067  				{
  2068  					Content:     "foo",
  2069  					Path:        "/path",
  2070  					Owner:       "root:root",
  2071  					Permissions: "0600",
  2072  				},
  2073  			},
  2074  			objects: []client.Object{testSecret},
  2075  		},
  2076  		"multiple files should work correctly": {
  2077  			cfg: &bootstrapv1.KubeadmConfig{
  2078  				Spec: bootstrapv1.KubeadmConfigSpec{
  2079  					Files: []bootstrapv1.File{
  2080  						{
  2081  							Content:     "bar",
  2082  							Path:        "/bar",
  2083  							Owner:       "root:root",
  2084  							Permissions: "0600",
  2085  						},
  2086  						{
  2087  							ContentFrom: &bootstrapv1.FileSource{
  2088  								Secret: bootstrapv1.SecretFileSource{
  2089  									Name: "source",
  2090  									Key:  "key",
  2091  								},
  2092  							},
  2093  							Path:        "/path",
  2094  							Owner:       "root:root",
  2095  							Permissions: "0600",
  2096  						},
  2097  					},
  2098  				},
  2099  			},
  2100  			expect: []bootstrapv1.File{
  2101  				{
  2102  					Content:     "bar",
  2103  					Path:        "/bar",
  2104  					Owner:       "root:root",
  2105  					Permissions: "0600",
  2106  				},
  2107  				{
  2108  					Content:     "foo",
  2109  					Path:        "/path",
  2110  					Owner:       "root:root",
  2111  					Permissions: "0600",
  2112  				},
  2113  			},
  2114  			objects: []client.Object{testSecret},
  2115  		},
  2116  	}
  2117  
  2118  	for name, tc := range cases {
  2119  		t.Run(name, func(t *testing.T) {
  2120  			g := NewWithT(t)
  2121  
  2122  			myclient := fake.NewClientBuilder().WithObjects(tc.objects...).Build()
  2123  			k := &KubeadmConfigReconciler{
  2124  				Client:              myclient,
  2125  				SecretCachingClient: myclient,
  2126  				KubeadmInitLock:     &myInitLocker{},
  2127  			}
  2128  
  2129  			// make a list of files we expect to be sourced from secrets
  2130  			// after we resolve files, assert that the original spec has
  2131  			// not been mutated and all paths we expected to be sourced
  2132  			// from secrets still are.
  2133  			contentFrom := map[string]bool{}
  2134  			for _, file := range tc.cfg.Spec.Files {
  2135  				if file.ContentFrom != nil {
  2136  					contentFrom[file.Path] = true
  2137  				}
  2138  			}
  2139  
  2140  			files, err := k.resolveFiles(ctx, tc.cfg)
  2141  			g.Expect(err).ToNot(HaveOccurred())
  2142  			g.Expect(files).To(BeComparableTo(tc.expect))
  2143  			for _, file := range tc.cfg.Spec.Files {
  2144  				if contentFrom[file.Path] {
  2145  					g.Expect(file.ContentFrom).NotTo(BeNil())
  2146  					g.Expect(file.Content).To(Equal(""))
  2147  				}
  2148  			}
  2149  		})
  2150  	}
  2151  }
  2152  
  2153  func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) {
  2154  	fakePasswd := "bar"
  2155  	testSecret := &corev1.Secret{
  2156  		ObjectMeta: metav1.ObjectMeta{
  2157  			Name: "source",
  2158  		},
  2159  		Data: map[string][]byte{
  2160  			"key": []byte(fakePasswd),
  2161  		},
  2162  	}
  2163  
  2164  	cases := map[string]struct {
  2165  		cfg     *bootstrapv1.KubeadmConfig
  2166  		objects []client.Object
  2167  		expect  []bootstrapv1.User
  2168  	}{
  2169  		"password should pass through": {
  2170  			cfg: &bootstrapv1.KubeadmConfig{
  2171  				Spec: bootstrapv1.KubeadmConfigSpec{
  2172  					Users: []bootstrapv1.User{
  2173  						{
  2174  							Name:   "foo",
  2175  							Passwd: &fakePasswd,
  2176  						},
  2177  					},
  2178  				},
  2179  			},
  2180  			expect: []bootstrapv1.User{
  2181  				{
  2182  					Name:   "foo",
  2183  					Passwd: &fakePasswd,
  2184  				},
  2185  			},
  2186  		},
  2187  		"passwdFrom should convert correctly": {
  2188  			cfg: &bootstrapv1.KubeadmConfig{
  2189  				Spec: bootstrapv1.KubeadmConfigSpec{
  2190  					Users: []bootstrapv1.User{
  2191  						{
  2192  							Name: "foo",
  2193  							PasswdFrom: &bootstrapv1.PasswdSource{
  2194  								Secret: bootstrapv1.SecretPasswdSource{
  2195  									Name: "source",
  2196  									Key:  "key",
  2197  								},
  2198  							},
  2199  						},
  2200  					},
  2201  				},
  2202  			},
  2203  			expect: []bootstrapv1.User{
  2204  				{
  2205  					Name:   "foo",
  2206  					Passwd: &fakePasswd,
  2207  				},
  2208  			},
  2209  			objects: []client.Object{testSecret},
  2210  		},
  2211  		"multiple users should work correctly": {
  2212  			cfg: &bootstrapv1.KubeadmConfig{
  2213  				Spec: bootstrapv1.KubeadmConfigSpec{
  2214  					Users: []bootstrapv1.User{
  2215  						{
  2216  							Name:   "foo",
  2217  							Passwd: &fakePasswd,
  2218  						},
  2219  						{
  2220  							Name: "bar",
  2221  							PasswdFrom: &bootstrapv1.PasswdSource{
  2222  								Secret: bootstrapv1.SecretPasswdSource{
  2223  									Name: "source",
  2224  									Key:  "key",
  2225  								},
  2226  							},
  2227  						},
  2228  					},
  2229  				},
  2230  			},
  2231  			expect: []bootstrapv1.User{
  2232  				{
  2233  					Name:   "foo",
  2234  					Passwd: &fakePasswd,
  2235  				},
  2236  				{
  2237  					Name:   "bar",
  2238  					Passwd: &fakePasswd,
  2239  				},
  2240  			},
  2241  			objects: []client.Object{testSecret},
  2242  		},
  2243  	}
  2244  
  2245  	for name, tc := range cases {
  2246  		t.Run(name, func(t *testing.T) {
  2247  			g := NewWithT(t)
  2248  
  2249  			myclient := fake.NewClientBuilder().WithObjects(tc.objects...).Build()
  2250  			k := &KubeadmConfigReconciler{
  2251  				Client:              myclient,
  2252  				SecretCachingClient: myclient,
  2253  				KubeadmInitLock:     &myInitLocker{},
  2254  			}
  2255  
  2256  			// make a list of password we expect to be sourced from secrets
  2257  			// after we resolve users, assert that the original spec has
  2258  			// not been mutated and all password we expected to be sourced
  2259  			// from secret still are.
  2260  			passwdFrom := map[string]bool{}
  2261  			for _, user := range tc.cfg.Spec.Users {
  2262  				if user.PasswdFrom != nil {
  2263  					passwdFrom[user.Name] = true
  2264  				}
  2265  			}
  2266  
  2267  			users, err := k.resolveUsers(ctx, tc.cfg)
  2268  			g.Expect(err).ToNot(HaveOccurred())
  2269  			g.Expect(users).To(BeComparableTo(tc.expect))
  2270  			for _, user := range tc.cfg.Spec.Users {
  2271  				if passwdFrom[user.Name] {
  2272  					g.Expect(user.PasswdFrom).NotTo(BeNil())
  2273  					g.Expect(user.Passwd).To(BeNil())
  2274  				}
  2275  			}
  2276  		})
  2277  	}
  2278  }
  2279  
  2280  // test utils.
  2281  
  2282  // newWorkerMachineForCluster returns a Machine with the passed Cluster's information and a pre-configured name.
  2283  func newWorkerMachineForCluster(cluster *clusterv1.Cluster) *clusterv1.Machine {
  2284  	return builder.Machine(cluster.Namespace, "worker-machine").
  2285  		WithVersion("v1.19.1").
  2286  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(cluster.Namespace, "conf1").Unstructured()).
  2287  		WithClusterName(cluster.Name).
  2288  		Build()
  2289  }
  2290  
  2291  // newControlPlaneMachine returns a Machine with the passed Cluster information and a MachineControlPlaneLabel.
  2292  func newControlPlaneMachine(cluster *clusterv1.Cluster, name string) *clusterv1.Machine {
  2293  	m := builder.Machine(cluster.Namespace, name).
  2294  		WithVersion("v1.19.1").
  2295  		WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "cfg").Unstructured()).
  2296  		WithClusterName(cluster.Name).
  2297  		WithLabels(map[string]string{clusterv1.MachineControlPlaneLabel: ""}).
  2298  		Build()
  2299  	return m
  2300  }
  2301  
  2302  // newMachinePool return a MachinePool object with the passed Cluster information and a basic bootstrap template.
  2303  func newMachinePool(cluster *clusterv1.Cluster, name string) *expv1.MachinePool {
  2304  	m := builder.MachinePool(cluster.Namespace, name).
  2305  		WithClusterName(cluster.Name).
  2306  		WithLabels(map[string]string{clusterv1.ClusterNameLabel: cluster.Name}).
  2307  		WithBootstrap(bootstrapbuilder.KubeadmConfig(cluster.Namespace, "conf1").Unstructured()).
  2308  		WithVersion("1.19.1").
  2309  		Build()
  2310  	return m
  2311  }
  2312  
  2313  // newWorkerMachinePoolForCluster returns a MachinePool with the passed Cluster's information and a pre-configured name.
  2314  func newWorkerMachinePoolForCluster(cluster *clusterv1.Cluster) *expv1.MachinePool {
  2315  	return newMachinePool(cluster, "worker-machinepool")
  2316  }
  2317  
  2318  // newKubeadmConfig return a CABPK KubeadmConfig object.
  2319  func newKubeadmConfig(namespace, name string) *bootstrapv1.KubeadmConfig {
  2320  	return bootstrapbuilder.KubeadmConfig(namespace, name).
  2321  		Build()
  2322  }
  2323  
  2324  // newKubeadmConfig return a CABPK KubeadmConfig object with a worker JoinConfiguration.
  2325  func newWorkerJoinKubeadmConfig(namespace, name string) *bootstrapv1.KubeadmConfig {
  2326  	return bootstrapbuilder.KubeadmConfig(namespace, name).
  2327  		WithJoinConfig(&bootstrapv1.JoinConfiguration{
  2328  			ControlPlane: nil,
  2329  		}).
  2330  		Build()
  2331  }
  2332  
  2333  // newKubeadmConfig returns a CABPK KubeadmConfig object with a ControlPlane JoinConfiguration.
  2334  func newControlPlaneJoinKubeadmConfig(namespace, name string) *bootstrapv1.KubeadmConfig {
  2335  	return bootstrapbuilder.KubeadmConfig(namespace, name).
  2336  		WithJoinConfig(&bootstrapv1.JoinConfiguration{
  2337  			ControlPlane: &bootstrapv1.JoinControlPlane{},
  2338  		}).
  2339  		Build()
  2340  }
  2341  
  2342  // newControlPlaneJoinConfig returns a CABPK KubeadmConfig object with a ControlPlane InitConfiguration and ClusterConfiguration.
  2343  func newControlPlaneInitKubeadmConfig(namespace, name string) *bootstrapv1.KubeadmConfig {
  2344  	return bootstrapbuilder.KubeadmConfig(namespace, name).
  2345  		WithInitConfig(&bootstrapv1.InitConfiguration{}).
  2346  		WithClusterConfig(&bootstrapv1.ClusterConfiguration{}).
  2347  		Build()
  2348  }
  2349  
  2350  // addKubeadmConfigToMachine adds the config details to the passed Machine, and adds the Machine to the KubeadmConfig as an ownerReference.
  2351  func addKubeadmConfigToMachine(config *bootstrapv1.KubeadmConfig, machine *clusterv1.Machine) {
  2352  	if machine == nil {
  2353  		panic("no machine passed to function")
  2354  	}
  2355  	config.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
  2356  		{
  2357  			Kind:       "Machine",
  2358  			APIVersion: clusterv1.GroupVersion.String(),
  2359  			Name:       machine.Name,
  2360  			UID:        types.UID(fmt.Sprintf("%s uid", machine.Name)),
  2361  		},
  2362  	}
  2363  
  2364  	if machine.Spec.Bootstrap.ConfigRef == nil {
  2365  		machine.Spec.Bootstrap.ConfigRef = &corev1.ObjectReference{}
  2366  	}
  2367  
  2368  	machine.Spec.Bootstrap.ConfigRef.Name = config.Name
  2369  	machine.Spec.Bootstrap.ConfigRef.Namespace = config.Namespace
  2370  }
  2371  
  2372  // addKubeadmConfigToMachine adds the config details to the passed MachinePool and adds the Machine to the KubeadmConfig as an ownerReference.
  2373  func addKubeadmConfigToMachinePool(config *bootstrapv1.KubeadmConfig, machinePool *expv1.MachinePool) {
  2374  	if machinePool == nil {
  2375  		panic("no machinePool passed to function")
  2376  	}
  2377  	config.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
  2378  		{
  2379  			Kind:       "MachinePool",
  2380  			APIVersion: expv1.GroupVersion.String(),
  2381  			Name:       machinePool.Name,
  2382  			UID:        types.UID(fmt.Sprintf("%s uid", machinePool.Name)),
  2383  		},
  2384  	}
  2385  	machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Name = config.Name
  2386  	machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Namespace = config.Namespace
  2387  }
  2388  
  2389  func createSecrets(t *testing.T, cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig) []client.Object {
  2390  	t.Helper()
  2391  
  2392  	out := []client.Object{}
  2393  	if config.Spec.ClusterConfiguration == nil {
  2394  		config.Spec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
  2395  	}
  2396  	certificates := secret.NewCertificatesForInitialControlPlane(config.Spec.ClusterConfiguration)
  2397  	if err := certificates.Generate(); err != nil {
  2398  		t.Fatal(err)
  2399  	}
  2400  	for _, certificate := range certificates {
  2401  		out = append(out, certificate.AsSecret(util.ObjectKey(cluster), *metav1.NewControllerRef(config, bootstrapv1.GroupVersion.WithKind("KubeadmConfig"))))
  2402  	}
  2403  	return out
  2404  }
  2405  
  2406  type myInitLocker struct {
  2407  	locked bool
  2408  }
  2409  
  2410  func (m *myInitLocker) Lock(_ context.Context, _ *clusterv1.Cluster, _ *clusterv1.Machine) bool {
  2411  	if !m.locked {
  2412  		m.locked = true
  2413  		return true
  2414  	}
  2415  	return false
  2416  }
  2417  
  2418  func (m *myInitLocker) Unlock(_ context.Context, _ *clusterv1.Cluster) bool {
  2419  	if m.locked {
  2420  		m.locked = false
  2421  	}
  2422  	return true
  2423  }
  2424  
  2425  func assertHasFalseCondition(g *WithT, myclient client.Client, req ctrl.Request, t clusterv1.ConditionType, s clusterv1.ConditionSeverity, r string) {
  2426  	config := &bootstrapv1.KubeadmConfig{
  2427  		ObjectMeta: metav1.ObjectMeta{
  2428  			Name:      req.Name,
  2429  			Namespace: req.Namespace,
  2430  		},
  2431  	}
  2432  
  2433  	configKey := client.ObjectKeyFromObject(config)
  2434  	g.Expect(myclient.Get(ctx, configKey, config)).To(Succeed())
  2435  	c := conditions.Get(config, t)
  2436  	g.Expect(c).ToNot(BeNil())
  2437  	g.Expect(c.Status).To(Equal(corev1.ConditionFalse))
  2438  	g.Expect(c.Severity).To(Equal(s))
  2439  	g.Expect(c.Reason).To(Equal(r))
  2440  }
  2441  
  2442  func assertHasTrueCondition(g *WithT, myclient client.Client, req ctrl.Request, t clusterv1.ConditionType) {
  2443  	config := &bootstrapv1.KubeadmConfig{
  2444  		ObjectMeta: metav1.ObjectMeta{
  2445  			Name:      req.Name,
  2446  			Namespace: req.Namespace,
  2447  		},
  2448  	}
  2449  	configKey := client.ObjectKeyFromObject(config)
  2450  	g.Expect(myclient.Get(ctx, configKey, config)).To(Succeed())
  2451  	c := conditions.Get(config, t)
  2452  	g.Expect(c).ToNot(BeNil())
  2453  	g.Expect(c.Status).To(Equal(corev1.ConditionTrue))
  2454  }