diff --git a/controlplane/kubeadm/internal/filters.go b/controlplane/kubeadm/internal/filters.go index 510a8290e4f0..b9d29d2054cf 100644 --- a/controlplane/kubeadm/internal/filters.go +++ b/controlplane/kubeadm/internal/filters.go @@ -163,109 +163,88 @@ func matchesKubeadmConfig(kubeadmConfigs map[string]*bootstrapv1.KubeadmConfig, return "", true, nil } - // Check if KCP and machine ClusterConfiguration matches, if not return - match, diff, err := matchClusterConfiguration(currentKubeadmConfig, kcp) + // takes the KubeadmConfigSpec from KCP and applies the transformations required + // to allow a comparison with the KubeadmConfig referenced from the machine. + desiredKubeadmConfig := getAdjustedKcpConfig(kcp, currentKubeadmConfig) + + desiredKubeadmConfigForDiff, currentKubeadmConfigForDiff, err := PrepareKubeadmConfigsForDiff(desiredKubeadmConfig, currentKubeadmConfig, kcp) if err != nil { return "", false, errors.Wrapf(err, "failed to match KubeadmConfig") } - if !match { - return fmt.Sprintf("Machine KubeadmConfig ClusterConfiguration is outdated: diff: %s", diff), false, nil - } - // Check if KCP and machine InitConfiguration or JoinConfiguration matches + // Check if current and desired KubeadmConfigs match. // NOTE: only one between init configuration and join configuration is set on a machine, depending // on the fact that the machine was the initial control plane node or a joining control plane node. - match, diff, err = matchInitOrJoinConfiguration(currentKubeadmConfig, kcp) + match, diff, err := compare.Diff(¤tKubeadmConfigForDiff.Spec, &desiredKubeadmConfigForDiff.Spec) if err != nil { return "", false, errors.Wrapf(err, "failed to match KubeadmConfig") } if !match { - return fmt.Sprintf("Machine KubeadmConfig InitConfiguration or JoinConfiguration are outdated: diff: %s", diff), false, nil + return fmt.Sprintf("Machine KubeadmConfig is outdated: diff: %s", diff), false, nil } return "", true, nil } -// matchClusterConfiguration verifies if KCP and machine ClusterConfiguration matches. -func matchClusterConfiguration(machineConfig *bootstrapv1.KubeadmConfig, kcp *controlplanev1.KubeadmControlPlane) (bool, string, error) { - if machineConfig == nil { - // Return true here because failing to get KubeadmConfig should not be considered as unmatching. - // This is a safety precaution to avoid rolling out machines if the client or the api-server is misbehaving. - return true, "", nil - } - machineConfig = machineConfig.DeepCopy() - - kcpLocalKubeadmConfig := kcp.Spec.KubeadmConfigSpec.DeepCopy() - if kcpLocalKubeadmConfig == nil { - kcpLocalKubeadmConfig = &bootstrapv1.KubeadmConfigSpec{} - } +// PrepareKubeadmConfigsForDiff cleans up all fields that are not relevant for the comparison. +func PrepareKubeadmConfigsForDiff(desiredKubeadmConfig, currentKubeadmConfig *bootstrapv1.KubeadmConfig, kcp *controlplanev1.KubeadmControlPlane) (desired *bootstrapv1.KubeadmConfig, current *bootstrapv1.KubeadmConfig, _ error) { + // DeepCopy to ensure the passed in KubeadmConfigs are not modified. + // This has to be done because we eventually want to be able to apply the desiredKubeadmConfig + // (without the modifications that we make here). + desiredKubeadmConfig = desiredKubeadmConfig.DeepCopy() + currentKubeadmConfig = currentKubeadmConfig.DeepCopy() // Default feature gates like in initializeControlPlane / scaleUpControlPlane. // Note: Changes in feature gates can still trigger rollouts. // TODO(in-place) refactor this area so the desired KubeadmConfig is not computed in multiple places independently. parsedVersion, err := semver.ParseTolerant(kcp.Spec.Version) if err != nil { - return false, "", errors.Wrapf(err, "failed to parse Kubernetes version %q", kcp.Spec.Version) + return nil, nil, errors.Wrapf(err, "failed to parse Kubernetes version %q", kcp.Spec.Version) } - DefaultFeatureGates(kcpLocalKubeadmConfig, parsedVersion) + DefaultFeatureGates(&desiredKubeadmConfig.Spec, parsedVersion) // Ignore ControlPlaneEndpoint which is added on the Machine KubeadmConfig by CABPK. // Note: ControlPlaneEndpoint should also never change for a Cluster, so no reason to trigger a rollout because of that. - machineConfig.Spec.ClusterConfiguration.ControlPlaneEndpoint = kcpLocalKubeadmConfig.ClusterConfiguration.ControlPlaneEndpoint + currentKubeadmConfig.Spec.ClusterConfiguration.ControlPlaneEndpoint = desiredKubeadmConfig.Spec.ClusterConfiguration.ControlPlaneEndpoint // Skip checking DNS fields because we can update the configuration of the working cluster in place. - machineConfig.Spec.ClusterConfiguration.DNS = kcpLocalKubeadmConfig.ClusterConfiguration.DNS - - // Drop differences that do not lead to changes to Machines, but that might exist due - // to changes in how we serialize objects or how webhooks work. - dropOmittableFields(kcpLocalKubeadmConfig) - dropOmittableFields(&machineConfig.Spec) - - // Compare and return. - match, diff, err := compare.Diff(machineConfig.Spec.ClusterConfiguration, kcpLocalKubeadmConfig.ClusterConfiguration) - if err != nil { - return false, "", errors.Wrapf(err, "failed to match ClusterConfiguration") - } - return match, diff, nil -} - -// matchInitOrJoinConfiguration verifies if KCP and machine InitConfiguration or JoinConfiguration matches. -// NOTE: By extension this method takes care of detecting changes in other fields of the KubeadmConfig configuration (e.g. Files, Mounts etc.) -func matchInitOrJoinConfiguration(machineConfig *bootstrapv1.KubeadmConfig, kcp *controlplanev1.KubeadmControlPlane) (bool, string, error) { - if machineConfig == nil { - // Return true here because failing to get KubeadmConfig should not be considered as unmatching. - // This is a safety precaution to avoid rolling out machines if the client or the api-server is misbehaving. - return true, "", nil - } - machineConfig = machineConfig.DeepCopy() - - // takes the KubeadmConfigSpec from KCP and applies the transformations required - // to allow a comparison with the KubeadmConfig referenced from the machine. - kcpConfig := getAdjustedKcpConfig(kcp, machineConfig) + currentKubeadmConfig.Spec.ClusterConfiguration.DNS = desiredKubeadmConfig.Spec.ClusterConfiguration.DNS // Default both KubeadmConfigSpecs before comparison. // *Note* This assumes that newly added default values never // introduce a semantic difference to the unset value. // But that is something that is ensured by our API guarantees. - defaulting.ApplyPreviousKubeadmConfigDefaults(kcpConfig) - defaulting.ApplyPreviousKubeadmConfigDefaults(&machineConfig.Spec) + defaulting.ApplyPreviousKubeadmConfigDefaults(&desiredKubeadmConfig.Spec) + defaulting.ApplyPreviousKubeadmConfigDefaults(¤tKubeadmConfig.Spec) - // Cleanup all fields that are not relevant for the comparison. - cleanupConfigFields(kcpConfig, machineConfig) + // Cleanup JoinConfiguration.Discovery from desiredKubeadmConfig and currentKubeadmConfig, because those info are relevant only for + // the join process and not for comparing the configuration of the machine. + // Note: Changes to Discovery will apply for the next join, but they will not lead to a rollout. + // Note: We should also not send Discovery.BootstrapToken.Token to a RuntimeExtension for security reasons. + desiredKubeadmConfig.Spec.JoinConfiguration.Discovery = bootstrapv1.Discovery{} + currentKubeadmConfig.Spec.JoinConfiguration.Discovery = bootstrapv1.Discovery{} - // Compare and return. - match, diff, err := compare.Diff(&machineConfig.Spec, kcpConfig) - if err != nil { - return false, "", errors.Wrapf(err, "failed to match InitConfiguration or JoinConfiguration") + // If KCP JoinConfiguration.ControlPlane is nil and the Machine JoinConfiguration.ControlPlane is empty, + // set Machine JoinConfiguration.ControlPlane to nil. + // NOTE: This is required because CABPK applies an empty JoinConfiguration.ControlPlane in case it is nil. + if desiredKubeadmConfig.Spec.JoinConfiguration.ControlPlane == nil && + reflect.DeepEqual(currentKubeadmConfig.Spec.JoinConfiguration.ControlPlane, &bootstrapv1.JoinControlPlane{}) { + currentKubeadmConfig.Spec.JoinConfiguration.ControlPlane = nil } - return match, diff, nil + + // Drop differences that do not lead to changes to Machines, but that might exist due + // to changes in how we serialize objects or how webhooks work. + dropOmittableFields(&desiredKubeadmConfig.Spec) + dropOmittableFields(¤tKubeadmConfig.Spec) + + return desiredKubeadmConfig, currentKubeadmConfig, nil } // getAdjustedKcpConfig takes the KubeadmConfigSpec from KCP and applies the transformations required // to allow a comparison with the KubeadmConfig referenced from the machine. // NOTE: The KCP controller applies a set of transformations when creating a KubeadmConfig referenced from the machine; // those transformations are implemented in ControlPlane.InitialControlPlaneConfig() and ControlPlane.JoinControlPlaneConfig(). -func getAdjustedKcpConfig(kcp *controlplanev1.KubeadmControlPlane, machineConfig *bootstrapv1.KubeadmConfig) *bootstrapv1.KubeadmConfigSpec { +func getAdjustedKcpConfig(kcp *controlplanev1.KubeadmControlPlane, machineConfig *bootstrapv1.KubeadmConfig) *bootstrapv1.KubeadmConfig { kcpConfig := kcp.Spec.KubeadmConfigSpec.DeepCopy() // if Machine's JoinConfiguration is set, this is a joining control plane machine, so empty out the InitConfiguration; @@ -278,36 +257,7 @@ func getAdjustedKcpConfig(kcp *controlplanev1.KubeadmControlPlane, machineConfig kcpConfig.JoinConfiguration = bootstrapv1.JoinConfiguration{} } - return kcpConfig -} - -// cleanupConfigFields cleanups all the fields that are not relevant for the comparison. -// Note: This function assumes that old defaults have been applied to kcpConfig and machineConfig -// as a consequence we can assume JoinConfiguration and JoinConfiguration.NodeRegistration are always defined. -func cleanupConfigFields(kcpConfig *bootstrapv1.KubeadmConfigSpec, machineConfig *bootstrapv1.KubeadmConfig) { - // KCP ClusterConfiguration will be compared in `matchClusterConfiguration` so we are cleaning it up here - // so it doesn't lead to a duplicate diff in `matchInitOrJoinConfiguration`. - kcpConfig.ClusterConfiguration = bootstrapv1.ClusterConfiguration{} - machineConfig.Spec.ClusterConfiguration = bootstrapv1.ClusterConfiguration{} - - // Cleanup JoinConfiguration.Discovery from kcpConfig and machineConfig, because those info are relevant only for - // the join process and not for comparing the configuration of the machine. - // Note: Changes to Discovery will apply for the next join, but they will not lead to a rollout. - kcpConfig.JoinConfiguration.Discovery = bootstrapv1.Discovery{} - machineConfig.Spec.JoinConfiguration.Discovery = bootstrapv1.Discovery{} - - // If KCP JoinConfiguration.ControlPlane is nil and the Machine JoinConfiguration.ControlPlane is empty, - // set Machine JoinConfiguration.ControlPlane to nil. - // NOTE: This is required because CABPK applies an empty JoinConfiguration.ControlPlane in case it is nil. - if kcpConfig.JoinConfiguration.ControlPlane == nil && - reflect.DeepEqual(machineConfig.Spec.JoinConfiguration.ControlPlane, &bootstrapv1.JoinControlPlane{}) { - machineConfig.Spec.JoinConfiguration.ControlPlane = nil - } - - // Drop differences that do not lead to changes to Machines, but that might exist due - // to changes in how we serialize objects or how webhooks work. - dropOmittableFields(kcpConfig) - dropOmittableFields(&machineConfig.Spec) + return &bootstrapv1.KubeadmConfig{Spec: *kcpConfig} } // dropOmittableFields makes the comparison tolerant to omittable fields being set in the go struct. It applies to: diff --git a/controlplane/kubeadm/internal/filters_test.go b/controlplane/kubeadm/internal/filters_test.go index 4456e5d3f079..3522a0c2322b 100644 --- a/controlplane/kubeadm/internal/filters_test.go +++ b/controlplane/kubeadm/internal/filters_test.go @@ -35,220 +35,6 @@ import ( "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/upstream" ) -func TestMatchClusterConfiguration(t *testing.T) { - t.Run("returns true if the machine does not have a bootstrap config", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{} - match, diff, err := matchClusterConfiguration(nil, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) - t.Run("Return true if cluster configuration matches", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "foo", - }, - }, - Version: "v1.30.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "foo", - }, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) - t.Run("Return false if cluster configuration does not match", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "foo", - }, - }, - Version: "v1.30.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "bar", - }, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeFalse()) - g.Expect(diff).To(BeComparableTo(`v1beta2.ClusterConfiguration{ - ... // 4 identical fields - Scheduler: {}, - DNS: {}, -- CertificatesDir: "bar", -+ CertificatesDir: "foo", - ImageRepository: "", - FeatureGates: nil, - ... // 2 identical fields - }`)) - }) - t.Run("Return true if only omittable fields are changed", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - FeatureGates: map[string]bool{}, - }, - }, - Version: "v1.30.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - FeatureGates: nil, - }, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) - t.Run("Return true if cluster configuration is empty (special case)", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{}, - Version: "v1.30.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) - t.Run("Return true although the FeatureGates were defaulted on the Machine KubeadmConfig", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - }, - Version: "v1.31.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - FeatureGates: map[string]bool{ - ControlPlaneKubeletLocalMode: true, - }, - }, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) - t.Run("Return true although the ControlPlaneEndpoint field is different", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - }, - Version: "v1.30.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - ControlPlaneEndpoint: "1.2.3.4:6443", - }, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) - t.Run("Return true although the DNS fields are different", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - DNS: bootstrapv1.DNS{ - ImageTag: "v1.10.1", - ImageRepository: "gcr.io/capi-test", - }, - }, - }, - Version: "v1.30.0", - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - DNS: bootstrapv1.DNS{ - ImageTag: "v1.9.3", - ImageRepository: "gcr.io/capi-test", - }, - }, - }, - } - match, diff, err := matchClusterConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) - }) -} - func TestGetAdjustedKcpConfig(t *testing.T) { t.Run("if the machine is the first control plane, kcp config should get InitConfiguration", func(t *testing.T) { g := NewWithT(t) @@ -278,8 +64,8 @@ func TestGetAdjustedKcpConfig(t *testing.T) { }, } kcpConfig := getAdjustedKcpConfig(kcp, machineConfig) - g.Expect(kcpConfig.InitConfiguration.IsDefined()).To(BeTrue()) - g.Expect(kcpConfig.JoinConfiguration.IsDefined()).To(BeFalse()) + g.Expect(kcpConfig.Spec.InitConfiguration.IsDefined()).To(BeTrue()) + g.Expect(kcpConfig.Spec.JoinConfiguration.IsDefined()).To(BeFalse()) }) t.Run("if the machine is a joining control plane, kcp config should get JoinConfiguration", func(t *testing.T) { g := NewWithT(t) @@ -309,128 +95,116 @@ func TestGetAdjustedKcpConfig(t *testing.T) { }, } kcpConfig := getAdjustedKcpConfig(kcp, machineConfig) - g.Expect(kcpConfig.InitConfiguration.IsDefined()).To(BeFalse()) - g.Expect(kcpConfig.JoinConfiguration.IsDefined()).To(BeTrue()) + g.Expect(kcpConfig.Spec.InitConfiguration.IsDefined()).To(BeFalse()) + g.Expect(kcpConfig.Spec.JoinConfiguration.IsDefined()).To(BeTrue()) }) } -func TestCleanupConfigFields(t *testing.T) { - t.Run("ClusterConfiguration gets removed from KcpConfig and MachineConfig", func(t *testing.T) { +func TestMatchesKubeadmConfig(t *testing.T) { + t.Run("returns true if Machine configRef is not defined", func(t *testing.T) { g := NewWithT(t) - kcpConfig := &bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "/tmp/certs", + m := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine", }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "/tmp/certs", + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + // ConfigRef not defined }, }, } - cleanupConfigFields(kcpConfig, machineConfig) - g.Expect(kcpConfig.ClusterConfiguration.IsDefined()).To(BeFalse()) - g.Expect(machineConfig.Spec.ClusterConfiguration.IsDefined()).To(BeFalse()) + reason, match, err := matchesKubeadmConfig(map[string]*bootstrapv1.KubeadmConfig{}, nil, m) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(match).To(BeTrue()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("JoinConfiguration.Discovery gets removed because it is not relevant for compare", func(t *testing.T) { + t.Run("returns true if Machine KubeadmConfig is not found", func(t *testing.T) { g := NewWithT(t) - kcpConfig := &bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - Discovery: bootstrapv1.Discovery{TLSBootstrapToken: "aaa"}, + m := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine", }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - Discovery: bootstrapv1.Discovery{TLSBootstrapToken: "aaa"}, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + APIGroup: bootstrapv1.GroupVersion.Group, + Kind: "KubeadmConfig", + Name: "test", + }, }, }, } - cleanupConfigFields(kcpConfig, machineConfig) - g.Expect(kcpConfig.JoinConfiguration.Discovery).To(BeComparableTo(bootstrapv1.Discovery{})) - g.Expect(machineConfig.Spec.JoinConfiguration.Discovery).To(BeComparableTo(bootstrapv1.Discovery{})) + reason, match, err := matchesKubeadmConfig(map[string]*bootstrapv1.KubeadmConfig{}, nil, m) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(match).To(BeTrue()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("JoinConfiguration.ControlPlane gets removed from MachineConfig if it was added by CABPK", func(t *testing.T) { + t.Run("returns true if ClusterConfiguration is equal", func(t *testing.T) { g := NewWithT(t) - kcpConfig := &bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - ControlPlane: nil, // Control plane configuration missing in KCP - }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - ControlPlane: &bootstrapv1.JoinControlPlane{}, // Machine gets a default JoinConfiguration.ControlPlane from CABPK + kcp := &controlplanev1.KubeadmControlPlane{ + Spec: controlplanev1.KubeadmControlPlaneSpec{ + KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + CertificatesDir: "foo", + }, }, + Version: "v1.30.0", }, } - cleanupConfigFields(kcpConfig, machineConfig) - g.Expect(kcpConfig.JoinConfiguration).ToNot(BeNil()) - g.Expect(machineConfig.Spec.JoinConfiguration.ControlPlane).To(BeNil()) - }) - t.Run("JoinConfiguration.ControlPlane gets not removed from MachineConfig if it is not empty", func(t *testing.T) { - g := NewWithT(t) - kcpConfig := &bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - ControlPlane: nil, // Control plane configuration missing in KCP + m := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine", }, - } - machineConfig := &bootstrapv1.KubeadmConfig{ - Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - ControlPlane: &bootstrapv1.JoinControlPlane{ - LocalAPIEndpoint: bootstrapv1.APIEndpoint{ - AdvertiseAddress: "1.2.3.4", - BindPort: 6443, - }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + APIGroup: bootstrapv1.GroupVersion.Group, + Kind: "KubeadmConfig", + Name: "test", }, }, }, } - cleanupConfigFields(kcpConfig, machineConfig) - g.Expect(kcpConfig.JoinConfiguration).ToNot(BeNil()) - g.Expect(machineConfig.Spec.JoinConfiguration.ControlPlane).ToNot(BeNil()) - }) - t.Run("drops omittable fields", func(t *testing.T) { - g := NewWithT(t) - kcpConfig := &bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - KubeletExtraArgs: []bootstrapv1.Arg{}, - }, - }, - } machineConfig := &bootstrapv1.KubeadmConfig{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - KubeletExtraArgs: []bootstrapv1.Arg{}, - }, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + CertificatesDir: "foo", }, }, } - cleanupConfigFields(kcpConfig, machineConfig) - g.Expect(kcpConfig.JoinConfiguration.NodeRegistration.KubeletExtraArgs).To(BeNil()) - g.Expect(machineConfig.Spec.JoinConfiguration.NodeRegistration.KubeletExtraArgs).To(BeNil()) - }) -} - -func TestMatchInitOrJoinConfiguration(t *testing.T) { - t.Run("returns true if the machine does not have a bootstrap config", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{} - match, diff, err := matchInitOrJoinConfiguration(nil, kcp) + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: machineConfig, + } + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("returns true if one format is empty and the other one cloud-config", func(t *testing.T) { + t.Run("returns true if ClusterConfiguration is equal (empty)", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - Format: bootstrapv1.CloudConfig, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, + }, + Version: "v1.30.0", + }, + } + m := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine", + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + APIGroup: bootstrapv1.GroupVersion.Group, + Kind: "KubeadmConfig", + Name: "test", + }, }, }, } @@ -440,22 +214,38 @@ func TestMatchInitOrJoinConfiguration(t *testing.T) { Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - Format: "", + ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: machineConfig, + } + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("returns true if InitConfiguration is equal", func(t *testing.T) { + t.Run("returns true if ClusterConfiguration is equal apart from defaulted FeatureGates field", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, + }, + Version: "v1.31.0", + }, + } + m := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine", + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + APIGroup: bootstrapv1.GroupVersion.Group, + Kind: "KubeadmConfig", + Name: "test", + }, }, }, } @@ -465,189 +255,133 @@ func TestMatchInitOrJoinConfiguration(t *testing.T) { Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{}, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + FeatureGates: map[string]bool{ + ControlPlaneKubeletLocalMode: true, + }, + }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: machineConfig, + } + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("returns false if InitConfiguration is NOT equal", func(t *testing.T) { + t.Run("returns true if ClusterConfiguration is equal apart from ControlPlaneEndpoint and DNS fields", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "A new name", // This is a change + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + DNS: bootstrapv1.DNS{ + ImageTag: "v1.10.1", + ImageRepository: "gcr.io/capi-test", }, }, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, }, + Version: "v1.30.0", }, } - machineConfig := &bootstrapv1.KubeadmConfig{ + m := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", + Name: "machine", }, - Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "An old name", // This is a change + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + APIGroup: bootstrapv1.GroupVersion.Group, + Kind: "KubeadmConfig", + Name: "test", }, }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeFalse()) - g.Expect(diff).To(BeComparableTo(`&v1beta2.KubeadmConfigSpec{ - ClusterConfiguration: {}, - InitConfiguration: v1beta2.InitConfiguration{ - BootstrapTokens: nil, - NodeRegistration: v1beta2.NodeRegistrationOptions{ -- Name: "An old name", -+ Name: "A new name", - CRISocket: "", - Taints: nil, - ... // 4 identical fields - }, - LocalAPIEndpoint: {}, - SkipPhases: nil, - ... // 2 identical fields - }, - JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, - Files: nil, - ... // 10 identical fields - }`)) - }) - t.Run("returns true if JoinConfiguration is equal", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, - }, - }, - } machineConfig := &bootstrapv1.KubeadmConfig{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{}, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + ControlPlaneEndpoint: "1.2.3.4:6443", + DNS: bootstrapv1.DNS{ + ImageTag: "v1.9.3", + ImageRepository: "gcr.io/capi-test", + }, + }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: machineConfig, + } + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("returns false if JoinConfiguration is NOT equal", func(t *testing.T) { + t.Run("returns false if ClusterConfiguration is NOT equal", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "A new name", // This is a change - }, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + CertificatesDir: "foo", }, }, + Version: "v1.30.0", }, } - machineConfig := &bootstrapv1.KubeadmConfig{ + m := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", + Name: "machine", }, - Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "An old name", // This is a change + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + APIGroup: bootstrapv1.GroupVersion.Group, + Kind: "KubeadmConfig", + Name: "test", }, }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeFalse()) - g.Expect(diff).To(BeComparableTo(`&v1beta2.KubeadmConfigSpec{ - ClusterConfiguration: {}, - InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, - JoinConfiguration: v1beta2.JoinConfiguration{ - NodeRegistration: v1beta2.NodeRegistrationOptions{ -- Name: "An old name", -+ Name: "A new name", - CRISocket: "", - Taints: nil, - ... // 4 identical fields - }, - CACertPath: "", - Discovery: {}, - ... // 4 identical fields - }, - Files: nil, - DiskSetup: {}, - ... // 9 identical fields - }`)) - }) - t.Run("returns false if JoinConfiguration is NOT equal", func(t *testing.T) { - g := NewWithT(t) - kcp := &controlplanev1.KubeadmControlPlane{ - Spec: controlplanev1.KubeadmControlPlaneSpec{ - KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{}, - // JoinConfiguration not set anymore. - }, - }, - } machineConfig := &bootstrapv1.KubeadmConfig{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "An old name", // This is a change - }, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + CertificatesDir: "bar", }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: machineConfig, + } + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeFalse()) - g.Expect(diff).To(BeComparableTo(`&v1beta2.KubeadmConfigSpec{ - ClusterConfiguration: {}, - InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, - JoinConfiguration: v1beta2.JoinConfiguration{ - NodeRegistration: v1beta2.NodeRegistrationOptions{ -- Name: "An old name", -+ Name: "", - CRISocket: "", - Taints: nil, - ... // 4 identical fields - }, - CACertPath: "", - Discovery: {}, + g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{ + ClusterConfiguration: v1beta2.ClusterConfiguration{ ... // 4 identical fields + Scheduler: {}, + DNS: {}, +- CertificatesDir: "bar", ++ CertificatesDir: "foo", + ImageRepository: "", + FeatureGates: nil, + ... // 2 identical fields }, - Files: nil, - DiskSetup: {}, - ... // 9 identical fields + InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, + JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, + ... // 11 identical fields }`)) }) - t.Run("returns true if returns true if only omittable configurations are not equal", func(t *testing.T) { + t.Run("returns true if InitConfiguration is equal", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ @@ -655,114 +389,163 @@ func TestMatchInitOrJoinConfiguration(t *testing.T) { ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, InitConfiguration: bootstrapv1.InitConfiguration{}, JoinConfiguration: bootstrapv1.JoinConfiguration{}, - Files: []bootstrapv1.File{}, // This is a change, but it is an omittable field and the diff between nil and empty array is not relevant. }, + Version: "v1.30.0", }, } - machineConfig := &bootstrapv1.KubeadmConfig{ + m := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, - Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{}, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + Kind: "KubeadmConfig", + Name: "test", + APIGroup: bootstrapv1.GroupVersion.Group, + }, + }, + }, + } + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: bootstrapv1.KubeadmConfigSpec{ + InitConfiguration: bootstrapv1.InitConfiguration{}, + }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeTrue()) - g.Expect(diff).To(BeEmpty()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("returns false if some other configurations are not equal", func(t *testing.T) { + t.Run("returns false if InitConfiguration is NOT equal", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, - Files: []bootstrapv1.File{{Path: "/tmp/foo"}}, // This is a change + InitConfiguration: bootstrapv1.InitConfiguration{ + NodeRegistration: bootstrapv1.NodeRegistrationOptions{ + Name: "A new name", // This is a change + }, + }, + JoinConfiguration: bootstrapv1.JoinConfiguration{}, }, + Version: "v1.30.0", }, } - machineConfig := &bootstrapv1.KubeadmConfig{ + m := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, - Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{}, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + Kind: "KubeadmConfig", + Name: "test", + APIGroup: bootstrapv1.GroupVersion.Group, + }, + }, + }, + } + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: bootstrapv1.KubeadmConfigSpec{ + InitConfiguration: bootstrapv1.InitConfiguration{ + NodeRegistration: bootstrapv1.NodeRegistrationOptions{ + Name: "An old name", // This is a change + }, + }, + }, }, } - match, diff, err := matchInitOrJoinConfiguration(machineConfig, kcp) + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeFalse()) - g.Expect(diff).To(BeComparableTo(`&v1beta2.KubeadmConfigSpec{ + g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{ ClusterConfiguration: {}, - InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, - JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, -- Files: nil, -+ Files: []v1beta2.File{{Path: "/tmp/foo"}}, - DiskSetup: {}, - Mounts: nil, - ... // 8 identical fields + InitConfiguration: v1beta2.InitConfiguration{ + BootstrapTokens: nil, + NodeRegistration: v1beta2.NodeRegistrationOptions{ +- Name: "An old name", ++ Name: "A new name", + CRISocket: "", + Taints: nil, + ... // 4 identical fields + }, + LocalAPIEndpoint: {}, + SkipPhases: nil, + ... // 2 identical fields + }, + JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, + Files: nil, + ... // 10 identical fields }`)) }) -} - -func TestMatchesKubeadmConfig(t *testing.T) { - t.Run("returns true if ClusterConfiguration is equal", func(t *testing.T) { + t.Run("returns true if JoinConfiguration is equal", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "foo", - }, + ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, + InitConfiguration: bootstrapv1.InitConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{}, }, Version: "v1.30.0", }, } m := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ - Name: "machine", + Namespace: "default", + Name: "test", }, Spec: clusterv1.MachineSpec{ Bootstrap: clusterv1.Bootstrap{ ConfigRef: clusterv1.ContractVersionedObjectReference{ - APIGroup: bootstrapv1.GroupVersion.Group, Kind: "KubeadmConfig", Name: "test", + APIGroup: bootstrapv1.GroupVersion.Group, }, }, }, } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "foo", + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: bootstrapv1.KubeadmConfigSpec{ + JoinConfiguration: bootstrapv1.JoinConfiguration{}, }, }, } - machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ - m.Name: machineConfig, - } reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeTrue()) g.Expect(reason).To(BeEmpty()) }) - t.Run("returns false if ClusterConfiguration is NOT equal", func(t *testing.T) { + t.Run("returns true if JoinConfiguration is equal apart from discovery", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "foo", + ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, + InitConfiguration: bootstrapv1.InitConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + // Gets removed because Discovery is not relevant for the rollout decision. + Discovery: bootstrapv1.Discovery{TLSBootstrapToken: "aaa"}, }, }, Version: "v1.30.0", @@ -770,54 +553,48 @@ func TestMatchesKubeadmConfig(t *testing.T) { } m := &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ - Name: "machine", + Namespace: "default", + Name: "test", }, Spec: clusterv1.MachineSpec{ Bootstrap: clusterv1.Bootstrap{ ConfigRef: clusterv1.ContractVersionedObjectReference{ - APIGroup: bootstrapv1.GroupVersion.Group, Kind: "KubeadmConfig", Name: "test", + APIGroup: bootstrapv1.GroupVersion.Group, }, }, }, } - machineConfig := &bootstrapv1.KubeadmConfig{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bootstrapv1.KubeadmConfigSpec{ - ClusterConfiguration: bootstrapv1.ClusterConfiguration{ - CertificatesDir: "bar", + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: bootstrapv1.KubeadmConfigSpec{ + JoinConfiguration: bootstrapv1.JoinConfiguration{ + // Gets removed because Discovery is not relevant for the rollout decision. + Discovery: bootstrapv1.Discovery{TLSBootstrapToken: "bbb"}, + }, }, }, } - machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ - m.Name: machineConfig, - } reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeFalse()) - g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig ClusterConfiguration is outdated: diff: v1beta2.ClusterConfiguration{ - ... // 4 identical fields - Scheduler: {}, - DNS: {}, -- CertificatesDir: "bar", -+ CertificatesDir: "foo", - ImageRepository: "", - FeatureGates: nil, - ... // 2 identical fields - }`)) + g.Expect(match).To(BeTrue()) + g.Expect(reason).To(BeEmpty()) }) - t.Run("returns true if InitConfiguration is equal", func(t *testing.T) { + t.Run("returns true if JoinConfiguration is equal apart from JoinControlPlane", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + ControlPlane: nil, // Control plane configuration missing in KCP + }, }, Version: "v1.30.0", }, @@ -844,7 +621,9 @@ func TestMatchesKubeadmConfig(t *testing.T) { Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + ControlPlane: &bootstrapv1.JoinControlPlane{}, // Machine gets a default JoinConfiguration.ControlPlane from CABPK + }, }, }, } @@ -853,18 +632,16 @@ func TestMatchesKubeadmConfig(t *testing.T) { g.Expect(match).To(BeTrue()) g.Expect(reason).To(BeEmpty()) }) - t.Run("returns false if InitConfiguration is NOT equal", func(t *testing.T) { + t.Run("returns false if JoinConfiguration has other differences in ControlPlane", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, - InitConfiguration: bootstrapv1.InitConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "A new name", // This is a change - }, + InitConfiguration: bootstrapv1.InitConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + ControlPlane: nil, // Control plane configuration missing in KCP }, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, }, Version: "v1.30.0", }, @@ -891,9 +668,12 @@ func TestMatchesKubeadmConfig(t *testing.T) { Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "An old name", // This is a change + JoinConfiguration: bootstrapv1.JoinConfiguration{ + ControlPlane: &bootstrapv1.JoinControlPlane{ + LocalAPIEndpoint: bootstrapv1.APIEndpoint{ + AdvertiseAddress: "1.2.3.4", + BindPort: 6443, + }, }, }, }, @@ -902,34 +682,38 @@ func TestMatchesKubeadmConfig(t *testing.T) { reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeFalse()) - g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig InitConfiguration or JoinConfiguration are outdated: diff: &v1beta2.KubeadmConfigSpec{ + g.Expect(reason).To(Equal(`Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{ ClusterConfiguration: {}, - InitConfiguration: v1beta2.InitConfiguration{ - BootstrapTokens: nil, - NodeRegistration: v1beta2.NodeRegistrationOptions{ -- Name: "An old name", -+ Name: "A new name", - CRISocket: "", - Taints: nil, - ... // 4 identical fields - }, - LocalAPIEndpoint: {}, - SkipPhases: nil, - ... // 2 identical fields + InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, + JoinConfiguration: v1beta2.JoinConfiguration{ + NodeRegistration: {ImagePullPolicy: "IfNotPresent"}, + CACertPath: "", + Discovery: {}, +- ControlPlane: &v1beta2.JoinControlPlane{ +- LocalAPIEndpoint: v1beta2.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 6443}, +- }, ++ ControlPlane: nil, + SkipPhases: nil, + Patches: {}, + Timeouts: {}, }, - JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, - Files: nil, - ... // 10 identical fields + Files: nil, + DiskSetup: {}, + ... // 9 identical fields }`)) }) - t.Run("returns true if JoinConfiguration is equal", func(t *testing.T) { + t.Run("returns false if JoinConfiguration is NOT equal", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + NodeRegistration: bootstrapv1.NodeRegistrationOptions{ + Name: "A new name", // This is a change + }, + }, }, Version: "v1.30.0", }, @@ -956,14 +740,36 @@ func TestMatchesKubeadmConfig(t *testing.T) { Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - JoinConfiguration: bootstrapv1.JoinConfiguration{}, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + NodeRegistration: bootstrapv1.NodeRegistrationOptions{ + Name: "An old name", // This is a change + }, + }, }, }, } reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(match).To(BeTrue()) - g.Expect(reason).To(BeEmpty()) + g.Expect(match).To(BeFalse()) + g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{ + ClusterConfiguration: {}, + InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, + JoinConfiguration: v1beta2.JoinConfiguration{ + NodeRegistration: v1beta2.NodeRegistrationOptions{ +- Name: "An old name", ++ Name: "A new name", + CRISocket: "", + Taints: nil, + ... // 4 identical fields + }, + CACertPath: "", + Discovery: {}, + ... // 4 identical fields + }, + Files: nil, + DiskSetup: {}, + ... // 9 identical fields + }`)) }) t.Run("returns false if JoinConfiguration is NOT equal", func(t *testing.T) { g := NewWithT(t) @@ -972,11 +778,7 @@ func TestMatchesKubeadmConfig(t *testing.T) { KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, InitConfiguration: bootstrapv1.InitConfiguration{}, - JoinConfiguration: bootstrapv1.JoinConfiguration{ - NodeRegistration: bootstrapv1.NodeRegistrationOptions{ - Name: "A new name", // This is a change - }, - }, + // JoinConfiguration not set anymore. }, Version: "v1.30.0", }, @@ -1014,13 +816,13 @@ func TestMatchesKubeadmConfig(t *testing.T) { reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeFalse()) - g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig InitConfiguration or JoinConfiguration are outdated: diff: &v1beta2.KubeadmConfigSpec{ + g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{ ClusterConfiguration: {}, InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, JoinConfiguration: v1beta2.JoinConfiguration{ NodeRegistration: v1beta2.NodeRegistrationOptions{ - Name: "An old name", -+ Name: "A new name", ++ Name: "", CRISocket: "", Taints: nil, ... // 4 identical fields @@ -1039,10 +841,63 @@ func TestMatchesKubeadmConfig(t *testing.T) { kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ + ClusterConfiguration: bootstrapv1.ClusterConfiguration{ + FeatureGates: map[string]bool{}, // This is a change, but it is an omittable field + }, + InitConfiguration: bootstrapv1.InitConfiguration{ + NodeRegistration: bootstrapv1.NodeRegistrationOptions{ + KubeletExtraArgs: []bootstrapv1.Arg{}, + }, + }, + JoinConfiguration: bootstrapv1.JoinConfiguration{ + NodeRegistration: bootstrapv1.NodeRegistrationOptions{ + KubeletExtraArgs: []bootstrapv1.Arg{}, + }, + }, + Files: []bootstrapv1.File{}, // This is a change, but it is an omittable field and the diff between nil and empty array is not relevant. + }, + Version: "v1.30.0", + }, + } + m := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + ConfigRef: clusterv1.ContractVersionedObjectReference{ + Kind: "KubeadmConfig", + Name: "test", + APIGroup: bootstrapv1.GroupVersion.Group, + }, + }, + }, + } + machineConfigs := map[string]*bootstrapv1.KubeadmConfig{ + m.Name: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: bootstrapv1.KubeadmConfigSpec{ ClusterConfiguration: bootstrapv1.ClusterConfiguration{}, InitConfiguration: bootstrapv1.InitConfiguration{}, JoinConfiguration: bootstrapv1.JoinConfiguration{}, - Files: []bootstrapv1.File{}, // This is a change, but it is an omittable field and the diff between nil and empty array is not relevant. + }, + }, + } + reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(match).To(BeTrue()) + g.Expect(reason).To(BeEmpty()) + }) + t.Run("returns true if KubeadmConfig is equal apart from defaulted format field", func(t *testing.T) { + g := NewWithT(t) + kcp := &controlplanev1.KubeadmControlPlane{ + Spec: controlplanev1.KubeadmControlPlaneSpec{ + KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ + Format: bootstrapv1.CloudConfig, }, Version: "v1.30.0", }, @@ -1069,7 +924,7 @@ func TestMatchesKubeadmConfig(t *testing.T) { Name: "test", }, Spec: bootstrapv1.KubeadmConfigSpec{ - InitConfiguration: bootstrapv1.InitConfiguration{}, + Format: "", }, }, } @@ -1078,7 +933,7 @@ func TestMatchesKubeadmConfig(t *testing.T) { g.Expect(match).To(BeTrue()) g.Expect(reason).To(BeEmpty()) }) - t.Run("returns false if some other configurations are not equal", func(t *testing.T) { + t.Run("returns false if KubeadmConfig is not equal (other configurations)", func(t *testing.T) { g := NewWithT(t) kcp := &controlplanev1.KubeadmControlPlane{ Spec: controlplanev1.KubeadmControlPlaneSpec{ @@ -1120,7 +975,7 @@ func TestMatchesKubeadmConfig(t *testing.T) { reason, match, err := matchesKubeadmConfig(machineConfigs, kcp, m) g.Expect(err).ToNot(HaveOccurred()) g.Expect(match).To(BeFalse()) - g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig InitConfiguration or JoinConfiguration are outdated: diff: &v1beta2.KubeadmConfigSpec{ + g.Expect(reason).To(BeComparableTo(`Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{ ClusterConfiguration: {}, InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}}, @@ -1584,7 +1439,7 @@ func TestUpToDate(t *testing.T) { infraConfigs: defaultInfraConfigs, machineConfigs: defaultMachineConfigs, expectUptoDate: false, - expectLogMessages: []string{"Machine KubeadmConfig ClusterConfiguration is outdated: diff: v1beta2.ClusterConfiguration{\n ... // 4 identical fields\n Scheduler: {},\n DNS: {},\n- CertificatesDir: \"foo\",\n+ CertificatesDir: \"bar\",\n ImageRepository: \"\",\n FeatureGates: nil,\n ... // 2 identical fields\n }"}, + expectLogMessages: []string{"Machine KubeadmConfig is outdated: diff: &v1beta2.KubeadmConfigSpec{\n ClusterConfiguration: v1beta2.ClusterConfiguration{\n ... // 4 identical fields\n Scheduler: {},\n DNS: {},\n- CertificatesDir: \"foo\",\n+ CertificatesDir: \"bar\",\n ImageRepository: \"\",\n FeatureGates: nil,\n ... // 2 identical fields\n },\n InitConfiguration: {NodeRegistration: {ImagePullPolicy: \"IfNotPresent\"}},\n JoinConfiguration: {NodeRegistration: {ImagePullPolicy: \"IfNotPresent\"}},\n ... // 11 identical fields\n }"}, expectConditionMessages: []string{"KubeadmConfig is not up-to-date"}, }, {