Skip to content

Commit

Permalink
Merge pull request #4292 from fabriziopandini/kubeadm-types-stop-gap
Browse files Browse the repository at this point in the history
✨ [CABPK] Support using kubeadm v1beta2 types internally
  • Loading branch information
k8s-ci-robot authored Mar 17, 2021
2 parents a554383 + c57d21e commit 226d998
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 9 deletions.
8 changes: 4 additions & 4 deletions bootstrap/kubeadm/controllers/kubeadmconfig_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
},
}
}
initdata, err := kubeadmv1beta1.ConfigurationToYAML(scope.Config.Spec.InitConfiguration)
initdata, err := kubeadmv1beta1.ConfigurationToYAMLForVersion(scope.Config.Spec.InitConfiguration, scope.ConfigOwner.KubernetesVersion())
if err != nil {
scope.Error(err, "Failed to marshal init configuration")
return ctrl.Result{}, err
Expand All @@ -397,7 +397,7 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
// injects into config.ClusterConfiguration values from top level object
r.reconcileTopLevelObjectSettings(scope.Cluster, machine, scope.Config)

clusterdata, err := kubeadmv1beta1.ConfigurationToYAML(scope.Config.Spec.ClusterConfiguration)
clusterdata, err := kubeadmv1beta1.ConfigurationToYAMLForVersion(scope.Config.Spec.ClusterConfiguration, scope.ConfigOwner.KubernetesVersion())
if err != nil {
scope.Error(err, "Failed to marshal cluster configuration")
return ctrl.Result{}, err
Expand Down Expand Up @@ -479,7 +479,7 @@ func (r *KubeadmConfigReconciler) joinWorker(ctx context.Context, scope *Scope)
return res, nil
}

joinData, err := kubeadmv1beta1.ConfigurationToYAML(scope.Config.Spec.JoinConfiguration)
joinData, err := kubeadmv1beta1.ConfigurationToYAMLForVersion(scope.Config.Spec.JoinConfiguration, scope.ConfigOwner.KubernetesVersion())
if err != nil {
scope.Error(err, "Failed to marshal join configuration")
return ctrl.Result{}, err
Expand Down Expand Up @@ -560,7 +560,7 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
return res, nil
}

joinData, err := kubeadmv1beta1.ConfigurationToYAML(scope.Config.Spec.JoinConfiguration)
joinData, err := kubeadmv1beta1.ConfigurationToYAMLForVersion(scope.Config.Spec.JoinConfiguration, scope.ConfigOwner.KubernetesVersion())
if err != nil {
scope.Error(err, "Failed to marshal join configuration")
return ctrl.Result{}, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,7 @@ func newMachine(cluster *clusterv1.Cluster, name string) *clusterv1.Machine {
APIVersion: bootstrapv1.GroupVersion.String(),
},
},
Version: pointer.StringPtr("v1.19.1"),
},
}
if cluster != nil {
Expand Down Expand Up @@ -1854,6 +1855,7 @@ func newMachinePool(cluster *clusterv1.Cluster, name string) *expv1.MachinePool
APIVersion: bootstrapv1.GroupVersion.String(),
},
},
Version: pointer.StringPtr("v1.19.1"),
},
},
},
Expand Down
60 changes: 60 additions & 0 deletions bootstrap/kubeadm/types/v1beta1/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,72 @@ limitations under the License.
package v1beta1

import (
"strings"

"github.com/pkg/errors"
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
versionutil "k8s.io/apimachinery/pkg/util/version"
"sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta2"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

func KubeVersionToKubeadmAPIGroupVersion(version string) (schema.GroupVersion, error) {
if version == "" {
return schema.GroupVersion{}, errors.New("version cannot be empty")
}
semVersion, err := versionutil.ParseSemantic(version)
if err != nil {
return schema.GroupVersion{}, errors.Wrap(err, "error parsing the Kubernetes version")
}
switch {
case semVersion.LessThan(versionutil.MustParseSemantic("v1.13.0")):
return schema.GroupVersion{}, errors.New("the bootstrap provider for kubeadm doesn't support Kubernetes version lower than v1.13.0")
case semVersion.LessThan(versionutil.MustParseSemantic("v1.15.0")):
// NOTE: All the Kubernetes version >= v1.13 and < v1.15 should use the kubeadm API version v1beta1
return GroupVersion, nil
default:
// NOTE: All the Kubernetes version greater or equal to v1.15 should use the kubeadm API version v1beta2.
// Also future Kubernetes versions (not yet released at the time of writing this code) are going to use v1beta2,
// no matter if kubeadm API versions newer than v1beta2 could be introduced by those release.
// This is acceptable because but v1beta2 will be supported by kubeadm until the deprecation cycle completes
// (9 months minimum after the deprecation date, not yet announced now); this gives Cluster API project time to
// introduce support for newer releases without blocking users to deploy newer version of Kubernetes.
return v1beta2.GroupVersion, nil
}
}

// ConfigurationToYAMLForVersion converts a kubeadm configuration type to its YAML
// representation.
func ConfigurationToYAMLForVersion(obj runtime.Object, k8sVersion string) (string, error) {
yamlBytes, err := MarshalToYamlForCodecs(obj, GroupVersion, GetCodecs())
if err != nil {
return "", errors.Wrap(err, "failed to marshal configuration")
}

yaml := string(yamlBytes)

// Fix the YAML according to the target Kubernetes version
// IMPORTANT: This is a stop-gap explicitly designed for back-porting on the v1alpha3 branch.
// This allows to unblock removal of the v1beta1 API in kubeadm by making Cluster API to use the v1beta2 kubeadm API
// under the assumption that the serialized version of the two APIs is equal as discussed; see
// "Insulate users from kubeadm API version changes" CAEP for more details.
// NOTE: This solution will stop to work when kubeadm will drop then v1beta2 kubeadm API, but this gives
// enough time (9/12 months from the deprecation date, not yet announced) for the users to migrate to
// the v1alpha4 release of Cluster API, where a proper conversion mechanism is going to be supported.
gv, err := KubeVersionToKubeadmAPIGroupVersion(k8sVersion)
if err != nil {
return "", err
}

if gv != GroupVersion {
yaml = strings.Replace(yaml, GroupVersion.String(), gv.String(), -1)
}

return yaml, nil
}

// GetCodecs returns a type that can be used to deserialize most kubeadm
// configuration types.
func GetCodecs() serializer.CodecFactory {
Expand Down
160 changes: 160 additions & 0 deletions bootstrap/kubeadm/types/v1beta1/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1beta1

import (
"testing"

"github.com/google/go-cmp/cmp"
. "github.com/onsi/gomega"

"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta2"
)

func Test_KubeVersionToKubeadmAPIGroupVersion(t *testing.T) {
type args struct {
k8sVersion string
}
tests := []struct {
name string
args args
want schema.GroupVersion
wantErr bool
}{
{
name: "fails when kubernetes version is too old",
args: args{
k8sVersion: "v1.12.0",
},
want: schema.GroupVersion{},
wantErr: true,
},
{
name: "pass with minimum kubernetes version for kubeadm API v1beta1",
args: args{
k8sVersion: "v1.13.0",
},
want: GroupVersion,
wantErr: false,
},
{
name: "pass with kubernetes version for kubeadm API v1beta1",
args: args{
k8sVersion: "v1.14.99",
},
want: GroupVersion,
wantErr: false,
},
{
name: "pass with minimum kubernetes version for kubeadm API v1beta2",
args: args{
k8sVersion: "v1.15.0",
},
want: v1beta2.GroupVersion,
wantErr: false,
},
{
name: "pass with kubernetes version for kubeadm API v1beta2",
args: args{
k8sVersion: "v1.20.99",
},
want: v1beta2.GroupVersion,
wantErr: false,
},
{
name: "pass with future kubernetes version",
args: args{
k8sVersion: "v99.99.99",
},
want: v1beta2.GroupVersion,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

got, err := KubeVersionToKubeadmAPIGroupVersion(tt.args.k8sVersion)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
g.Expect(err).ToNot(HaveOccurred())
g.Expect(got).To(Equal(tt.want))
})
}
}

func TestConfigurationToYAMLForVersion(t *testing.T) {
type args struct {
obj *ClusterConfiguration
k8sVersion string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "Generates a v1beta1 kubeadm configuration with Kubernetes versions < 1.15",
args: args{
obj: &ClusterConfiguration{},
k8sVersion: "v1.14.9",
},
want: "apiServer: {}\n" +
"apiVersion: kubeadm.k8s.io/v1beta1\n" + "" +
"controllerManager: {}\n" +
"dns: {}\n" +
"etcd: {}\n" +
"kind: ClusterConfiguration\n" +
"networking: {}\n" +
"scheduler: {}\n",
wantErr: false,
},
{
name: "Generates a v1beta2 kubeadm configuration with Kubernetes versions >= 1.15",
args: args{
obj: &ClusterConfiguration{},
k8sVersion: "v1.15.0",
},
want: "apiServer: {}\n" +
"apiVersion: kubeadm.k8s.io/v1beta2\n" + "" +
"controllerManager: {}\n" +
"dns: {}\n" +
"etcd: {}\n" +
"kind: ClusterConfiguration\n" +
"networking: {}\n" +
"scheduler: {}\n",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

got, err := ConfigurationToYAMLForVersion(tt.args.obj, tt.args.k8sVersion)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
g.Expect(err).ToNot(HaveOccurred())
g.Expect(got).To(Equal(tt.want), cmp.Diff(tt.want, got))
})
}
}
26 changes: 26 additions & 0 deletions bootstrap/kubeadm/types/v1beta2/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1beta2

import (
"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "kubeadm.k8s.io", Version: "v1beta2"}
)
14 changes: 14 additions & 0 deletions bootstrap/util/configowner.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ func (co ConfigOwner) IsMachinePool() bool {
return co.GetKind() == "MachinePool"
}

// Returns the Kuberentes version for the config owner object
func (co ConfigOwner) KubernetesVersion() string {
fields := []string{"spec", "version"}
if co.IsMachinePool() {
fields = []string{"spec", "template", "spec", "version"}
}

version, _, err := unstructured.NestedString(co.Object, fields...)
if err != nil {
return ""
}
return version
}

// GetConfigOwner returns the Unstructured object owning the current resource.
func GetConfigOwner(ctx context.Context, c client.Client, obj metav1.Object) (*ConfigOwner, error) {
allowedGKs := []schema.GroupKind{
Expand Down
10 changes: 10 additions & 0 deletions bootstrap/util/configowner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func TestGetConfigOwner(t *testing.T) {
Bootstrap: clusterv1.Bootstrap{
DataSecretName: pointer.StringPtr("my-data-secret"),
},
Version: pointer.StringPtr("v1.19.6"),
},
Status: clusterv1.MachineStatus{
InfrastructureReady: true,
Expand All @@ -80,6 +81,8 @@ func TestGetConfigOwner(t *testing.T) {
g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
g.Expect(configOwner.IsControlPlaneMachine()).To(BeTrue())
g.Expect(configOwner.IsMachinePool()).To(BeFalse())
g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
g.Expect(*configOwner.DataSecretName()).To(BeEquivalentTo("my-data-secret"))
})

Expand All @@ -97,6 +100,11 @@ func TestGetConfigOwner(t *testing.T) {
},
Spec: expv1.MachinePoolSpec{
ClusterName: "my-cluster",
Template: clusterv1.MachineTemplateSpec{
Spec: clusterv1.MachineSpec{
Version: pointer.StringPtr("v1.19.6"),
},
},
},
Status: expv1.MachinePoolStatus{
InfrastructureReady: true,
Expand All @@ -123,6 +131,8 @@ func TestGetConfigOwner(t *testing.T) {
g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
g.Expect(configOwner.IsControlPlaneMachine()).To(BeFalse())
g.Expect(configOwner.IsMachinePool()).To(BeTrue())
g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
g.Expect(configOwner.DataSecretName()).To(BeNil())
})

Expand Down
Loading

0 comments on commit 226d998

Please sign in to comment.