Skip to content

⚠️ Implement v1beta2 contract in cluster controller, KCP, CABPK #12094

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

13 changes: 13 additions & 0 deletions bootstrap/kubeadm/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ func Convert_v1beta2_KubeadmConfigStatus_To_v1beta1_KubeadmConfigStatus(in *boot
out.FailureMessage = in.Deprecated.V1Beta1.FailureMessage
}

// Move initialization to old fields
if in.Initialization != nil {
out.Ready = in.Initialization.DataSecretCreated
}

// Move new conditions (v1beta2) to the v1beta2 field.
if in.Conditions == nil {
return nil
Expand Down Expand Up @@ -103,6 +108,14 @@ func Convert_v1beta1_KubeadmConfigStatus_To_v1beta2_KubeadmConfigStatus(in *Kube
}
out.Deprecated.V1Beta1.FailureReason = in.FailureReason
out.Deprecated.V1Beta1.FailureMessage = in.FailureMessage

// Move ready to Initialization
if in.Ready {
if out.Initialization == nil {
out.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{}
}
out.Initialization.DataSecretCreated = in.Ready
}
return nil
}

Expand Down
7 changes: 7 additions & 0 deletions bootstrap/kubeadm/api/v1beta1/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ func hubKubeadmConfigStatus(in *bootstrapv1.KubeadmConfigStatus, c fuzz.Continue
if in.Deprecated.V1Beta1 == nil {
in.Deprecated.V1Beta1 = &bootstrapv1.KubeadmConfigV1Beta1DeprecatedStatus{}
}

// Drop empty structs with only omit empty fields.
if in.Initialization != nil {
if reflect.DeepEqual(in.Initialization, &bootstrapv1.KubeadmConfigInitializationStatus{}) {
in.Initialization = nil
}
}
}

func spokeKubeadmConfigStatus(in *KubeadmConfigStatus, c fuzz.Continue) {
Expand Down
4 changes: 2 additions & 2 deletions bootstrap/kubeadm/api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions bootstrap/kubeadm/api/v1beta2/kubeadmconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,9 +460,10 @@ type KubeadmConfigStatus struct {
// +kubebuilder:validation:MaxItems=32
Conditions []metav1.Condition `json:"conditions,omitempty"`

// ready indicates the BootstrapData field is ready to be consumed
// initialization provides observations of the KubeadmConfig initialization process.
// NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial Machine provisioning.
// +optional
Ready bool `json:"ready"`
Initialization *KubeadmConfigInitializationStatus `json:"initialization,omitempty"`

// dataSecretName is the name of the secret that stores the bootstrap data script.
// +optional
Expand All @@ -479,6 +480,14 @@ type KubeadmConfigStatus struct {
Deprecated *KubeadmConfigDeprecatedStatus `json:"deprecated,omitempty"`
}

// KubeadmConfigInitializationStatus provides observations of the KubeadmConfig initialization process.
type KubeadmConfigInitializationStatus struct {
// dataSecretCreated is true when the Machine's boostrap secret is created.
// NOTE: this field is part of the Cluster API contract, and it is used to orchestrate initial Machine provisioning.
// +optional
DataSecretCreated bool `json:"dataSecretCreated,omitempty"`
}

// KubeadmConfigDeprecatedStatus groups all the status fields that are deprecated and will be removed in a future version.
// See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context.
type KubeadmConfigDeprecatedStatus struct {
Expand Down
20 changes: 20 additions & 0 deletions bootstrap/kubeadm/api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bootstrap/kubeadm/config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
labels:
- pairs:
cluster.x-k8s.io/v1beta1: v1beta2
cluster.x-k8s.io/v1beta2: v1beta2

# This kustomization.yaml is not intended to be run by itself,
# since it depends on service name and namespace that are out of this kustomize package.
Expand Down
14 changes: 10 additions & 4 deletions bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,11 @@ func (r *KubeadmConfigReconciler) reconcile(ctx context.Context, scope *Scope, c
return ctrl.Result{}, nil
// Reconcile status for machines that already have a secret reference, but our status isn't up to date.
// This case solves the pivoting scenario (or a backup restore) which doesn't preserve the status subresource on objects.
case configOwner.DataSecretName() != nil && (!config.Status.Ready || config.Status.DataSecretName == nil):
config.Status.Ready = true
case configOwner.DataSecretName() != nil && (!(config.Status.Initialization != nil && config.Status.Initialization.DataSecretCreated) || config.Status.DataSecretName == nil):
if config.Status.Initialization == nil {
config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{}
}
config.Status.Initialization.DataSecretCreated = true
config.Status.DataSecretName = configOwner.DataSecretName()
v1beta1conditions.MarkTrue(config, bootstrapv1.DataSecretAvailableCondition)
conditions.Set(scope.Config, metav1.Condition{
Expand All @@ -321,7 +324,7 @@ func (r *KubeadmConfigReconciler) reconcile(ctx context.Context, scope *Scope, c
return ctrl.Result{}, nil
// Status is ready means a config has been generated.
// This also solves the upgrade scenario to a version which includes v1beta2 to ensure v1beta2 conditions are properly set.
case config.Status.Ready:
case config.Status.Initialization != nil && config.Status.Initialization.DataSecretCreated:
// Based on existing code paths status.Ready is only true if status.dataSecretName is set
// So we can assume that the DataSecret is available.
v1beta1conditions.MarkTrue(config, bootstrapv1.DataSecretAvailableCondition)
Expand Down Expand Up @@ -1416,7 +1419,10 @@ func (r *KubeadmConfigReconciler) storeBootstrapData(ctx context.Context, scope
}
}
scope.Config.Status.DataSecretName = ptr.To(secret.Name)
scope.Config.Status.Ready = true
if scope.Config.Status.Initialization == nil {
scope.Config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{}
}
scope.Config.Status.Initialization.DataSecretCreated = true
v1beta1conditions.MarkTrue(scope.Config, bootstrapv1.DataSecretAvailableCondition)
conditions.Set(scope.Config, metav1.Condition{
Type: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Condition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func TestKubeadmConfigReconciler_Reconcile_ReturnEarlyIfKubeadmConfigIsReady(t *
config := newKubeadmConfig(metav1.NamespaceDefault, "cfg")
addKubeadmConfigToMachine(config, machine)

config.Status.Ready = true
config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{DataSecretCreated: true}

objects := []client.Object{
cluster,
Expand Down Expand Up @@ -157,7 +157,7 @@ func TestKubeadmConfigReconciler_TestSecretOwnerReferenceReconciliation(t *testi
},
Type: corev1.SecretTypeBootstrapToken,
}
config.Status.Ready = true
config.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{DataSecretCreated: true}

objects := []client.Object{
config,
Expand Down Expand Up @@ -532,7 +532,8 @@ func TestKubeadmConfigReconciler_Reconcile_GenerateCloudConfigData(t *testing.T)

cfg, err := getKubeadmConfig(myclient, "control-plane-init-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
assertHasTrueCondition(g, myclient, request, bootstrapv1.CertificatesAvailableCondition)
Expand Down Expand Up @@ -714,7 +715,8 @@ func TestReconcileIfJoinCertificatesAvailableConditioninNodesAndControlPlaneIsRe

cfg, err := getKubeadmConfig(myclient, rt.configName, metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
assertHasTrueCondition(g, myclient, request, bootstrapv1.DataSecretAvailableCondition)
Expand Down Expand Up @@ -791,7 +793,8 @@ func TestReconcileIfJoinNodePoolsAndControlPlaneIsReady(t *testing.T) {

cfg, err := getKubeadmConfig(myclient, rt.configName, metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())

Expand Down Expand Up @@ -892,7 +895,8 @@ func TestBootstrapDataFormat(t *testing.T) {
// Verify the KubeadmConfig resource state is correct.
cfg, err := getKubeadmConfig(myclient, configName, metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())

// Read the secret containing the bootstrap data which was generated by the
Expand Down Expand Up @@ -997,7 +1001,8 @@ func TestKubeadmConfigSecretCreatedStatusNotPatched(t *testing.T) {

cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
}
Expand Down Expand Up @@ -1051,7 +1056,8 @@ func TestBootstrapTokenTTLExtension(t *testing.T) {

cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())

Expand All @@ -1067,7 +1073,8 @@ func TestBootstrapTokenTTLExtension(t *testing.T) {

cfg, err = getKubeadmConfig(myclient, "control-plane-join-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())

Expand Down Expand Up @@ -1297,7 +1304,8 @@ func TestBootstrapTokenRotationMachinePool(t *testing.T) {

cfg, err := getKubeadmConfig(myclient, "workerpool-join-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())

Expand Down Expand Up @@ -1489,7 +1497,8 @@ func TestBootstrapTokenRefreshIfTokenSecretCleaned(t *testing.T) {

cfg, err := getKubeadmConfig(myclient, "worker-join-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
g.Expect(cfg.Spec.JoinConfiguration.Discovery.BootstrapToken.Token).ToNot(BeEmpty())
Expand Down Expand Up @@ -1562,7 +1571,8 @@ func TestBootstrapTokenRefreshIfTokenSecretCleaned(t *testing.T) {

cfg, err := getKubeadmConfig(myclient, "workerpool-join-cfg", metav1.NamespaceDefault)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.Status.Ready).To(BeTrue())
g.Expect(cfg.Status.Initialization).ToNot(BeNil())
g.Expect(cfg.Status.Initialization.DataSecretCreated).To(BeTrue())
g.Expect(cfg.Status.DataSecretName).NotTo(BeNil())
g.Expect(cfg.Status.ObservedGeneration).NotTo(BeNil())
g.Expect(cfg.Spec.JoinConfiguration.Discovery.BootstrapToken.Token).ToNot(BeEmpty())
Expand Down Expand Up @@ -2791,7 +2801,7 @@ func TestKubeadmConfigReconciler_Reconcile_v1beta2_conditions(t *testing.T) {
name: "conditions should be true after upgrading to v1beta2",
config: func() *bootstrapv1.KubeadmConfig {
c := kubeadmConfig.DeepCopy()
c.Status.Ready = true
c.Status.Initialization = &bootstrapv1.KubeadmConfigInitializationStatus{DataSecretCreated: true}
return c
}(),
machine: machine.DeepCopy(),
Expand Down
10 changes: 0 additions & 10 deletions controllers/external/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,3 @@ func IsReady(obj *unstructured.Unstructured) (bool, error) {
}
return ready && found, nil
}

// IsInitialized returns true if the Status.Initialized field on an external object is true.
func IsInitialized(obj *unstructured.Unstructured) (bool, error) {
initialized, found, err := unstructured.NestedBool(obj.Object, "status", "initialized")
if err != nil {
return false, errors.Wrapf(err, "failed to determine %v %q initialized",
obj.GroupVersionKind(), obj.GetName())
}
return initialized && found, nil
}
14 changes: 14 additions & 0 deletions controlplane/kubeadm/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ func Convert_v1beta2_KubeadmControlPlaneStatus_To_v1beta1_KubeadmControlPlaneSta
out.UnavailableReplicas = in.Deprecated.V1Beta1.UnavailableReplicas
}

// Move initialized to ControlPlaneInitialized, rebuild ready
if in.Initialization != nil {
out.Initialized = in.Initialization.ControlPlaneInitialized
}
out.Ready = in.Deprecated.V1Beta1.ReadyReplicas > 0

// Move new conditions (v1beta2) and replica counter to the v1beta2 field.
if in.Conditions == nil && in.ReadyReplicas == nil && in.AvailableReplicas == nil && in.UpToDateReplicas == nil {
return nil
Expand Down Expand Up @@ -125,6 +131,14 @@ func Convert_v1beta1_KubeadmControlPlaneStatus_To_v1beta2_KubeadmControlPlaneSta
out.Deprecated.V1Beta1.UpdatedReplicas = in.UpdatedReplicas
out.Deprecated.V1Beta1.ReadyReplicas = in.ReadyReplicas
out.Deprecated.V1Beta1.UnavailableReplicas = in.UnavailableReplicas

// Move initialized to ControlPlaneInitialized
if in.Initialized {
if out.Initialization == nil {
out.Initialization = &controlplanev1.KubeadmControlPlaneInitializationStatus{}
}
out.Initialization.ControlPlaneInitialized = in.Initialized
}
return nil
}

Expand Down
10 changes: 10 additions & 0 deletions controlplane/kubeadm/api/v1beta1/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ func hubKubeadmControlPlaneStatus(in *controlplanev1.KubeadmControlPlaneStatus,
if in.Deprecated.V1Beta1 == nil {
in.Deprecated.V1Beta1 = &controlplanev1.KubeadmControlPlaneV1Beta1DeprecatedStatus{}
}

// Drop empty structs with only omit empty fields.
if in.Initialization != nil {
if reflect.DeepEqual(in.Initialization, &controlplanev1.KubeadmControlPlaneInitializationStatus{}) {
in.Initialization = nil
}
}
}

func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Continue) {
Expand All @@ -70,4 +77,7 @@ func spokeKubeadmControlPlaneStatus(in *KubeadmControlPlaneStatus, c fuzz.Contin
in.V1Beta2 = nil
}
}

// Make sure ready is consistent with ready replicas, so we can rebuild the info after the round trip.
in.Ready = in.ReadyReplicas > 0
}
Loading