sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/types/utils.go (about)

     1  /*
     2  Copyright 2021 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 utils contains Kubeadm utility types.
    18  package utils
    19  
    20  import (
    21  	"github.com/blang/semver/v4"
    22  	"github.com/pkg/errors"
    23  	"k8s.io/apimachinery/pkg/runtime"
    24  	"k8s.io/apimachinery/pkg/runtime/schema"
    25  	"k8s.io/apimachinery/pkg/runtime/serializer"
    26  	"sigs.k8s.io/controller-runtime/pkg/conversion"
    27  	"sigs.k8s.io/controller-runtime/pkg/scheme"
    28  
    29  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    30  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/upstreamv1beta2"
    31  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/upstreamv1beta3"
    32  	"sigs.k8s.io/cluster-api/util/version"
    33  )
    34  
    35  var (
    36  	v1beta2KubeadmVersion = semver.MustParse("1.15.0")
    37  	v1beta3KubeadmVersion = semver.MustParse("1.22.0")
    38  
    39  	clusterConfigurationVersionTypeMap = map[schema.GroupVersion]conversion.Convertible{
    40  		upstreamv1beta3.GroupVersion: &upstreamv1beta3.ClusterConfiguration{},
    41  		upstreamv1beta2.GroupVersion: &upstreamv1beta2.ClusterConfiguration{},
    42  	}
    43  
    44  	clusterStatusVersionTypeMap = map[schema.GroupVersion]conversion.Convertible{
    45  		// ClusterStatus has been removed in v1beta3, so we don't need an entry for v1beta3
    46  		upstreamv1beta2.GroupVersion: &upstreamv1beta2.ClusterStatus{},
    47  	}
    48  
    49  	initConfigurationVersionTypeMap = map[schema.GroupVersion]conversion.Convertible{
    50  		upstreamv1beta3.GroupVersion: &upstreamv1beta3.InitConfiguration{},
    51  		upstreamv1beta2.GroupVersion: &upstreamv1beta2.InitConfiguration{},
    52  	}
    53  
    54  	joinConfigurationVersionTypeMap = map[schema.GroupVersion]conversion.Convertible{
    55  		upstreamv1beta3.GroupVersion: &upstreamv1beta3.JoinConfiguration{},
    56  		upstreamv1beta2.GroupVersion: &upstreamv1beta2.JoinConfiguration{},
    57  	}
    58  )
    59  
    60  // KubeVersionToKubeadmAPIGroupVersion maps a Kubernetes version to the correct Kubeadm API Group supported.
    61  func KubeVersionToKubeadmAPIGroupVersion(v semver.Version) (schema.GroupVersion, error) {
    62  	switch {
    63  	case version.Compare(v, v1beta2KubeadmVersion, version.WithoutPreReleases()) < 0:
    64  		return schema.GroupVersion{}, errors.New("the bootstrap provider for kubeadm doesn't support Kubernetes version lower than v1.15.0")
    65  	case version.Compare(v, v1beta3KubeadmVersion, version.WithoutPreReleases()) < 0:
    66  		// NOTE: All the Kubernetes version >= v1.15 and < v1.22 should use the kubeadm API version v1beta2
    67  		return upstreamv1beta2.GroupVersion, nil
    68  	default:
    69  		// NOTE: All the Kubernetes version greater or equal to v1.22 should use the kubeadm API version v1beta3.
    70  		// Also future Kubernetes versions (not yet released at the time of writing this code) are going to use v1beta3,
    71  		// no matter if kubeadm API versions newer than v1beta3 could be introduced by those release.
    72  		// This is acceptable because v1beta3 will be supported by kubeadm until the deprecation cycle completes
    73  		// (9 months minimum after the deprecation date, not yet announced now); this gives Cluster API project time to
    74  		// introduce support for newer releases without blocking users to deploy newer version of Kubernetes.
    75  		return upstreamv1beta3.GroupVersion, nil
    76  	}
    77  }
    78  
    79  // MarshalClusterConfigurationForVersion converts a Cluster API ClusterConfiguration type to the kubeadm API type
    80  // for the given Kubernetes Version.
    81  // NOTE: This assumes Kubernetes Version equals to kubeadm version.
    82  func MarshalClusterConfigurationForVersion(obj *bootstrapv1.ClusterConfiguration, version semver.Version) (string, error) {
    83  	return marshalForVersion(obj, version, clusterConfigurationVersionTypeMap)
    84  }
    85  
    86  // MarshalClusterStatusForVersion converts a Cluster API ClusterStatus type to the kubeadm API type
    87  // for the given Kubernetes Version.
    88  // NOTE: This assumes Kubernetes Version equals to kubeadm version.
    89  func MarshalClusterStatusForVersion(obj *bootstrapv1.ClusterStatus, version semver.Version) (string, error) {
    90  	return marshalForVersion(obj, version, clusterStatusVersionTypeMap)
    91  }
    92  
    93  // MarshalInitConfigurationForVersion converts a Cluster API InitConfiguration type to the kubeadm API type
    94  // for the given Kubernetes Version.
    95  // NOTE: This assumes Kubernetes Version equals to kubeadm version.
    96  func MarshalInitConfigurationForVersion(obj *bootstrapv1.InitConfiguration, version semver.Version) (string, error) {
    97  	return marshalForVersion(obj, version, initConfigurationVersionTypeMap)
    98  }
    99  
   100  // MarshalJoinConfigurationForVersion converts a Cluster API JoinConfiguration type to the kubeadm API type
   101  // for the given Kubernetes Version.
   102  // NOTE: This assumes Kubernetes Version equals to kubeadm version.
   103  func MarshalJoinConfigurationForVersion(obj *bootstrapv1.JoinConfiguration, version semver.Version) (string, error) {
   104  	return marshalForVersion(obj, version, joinConfigurationVersionTypeMap)
   105  }
   106  
   107  func marshalForVersion(obj conversion.Hub, version semver.Version, kubeadmObjVersionTypeMap map[schema.GroupVersion]conversion.Convertible) (string, error) {
   108  	kubeadmAPIGroupVersion, err := KubeVersionToKubeadmAPIGroupVersion(version)
   109  	if err != nil {
   110  		return "", err
   111  	}
   112  
   113  	targetKubeadmObj, ok := kubeadmObjVersionTypeMap[kubeadmAPIGroupVersion]
   114  	if !ok {
   115  		return "", errors.Errorf("missing KubeadmAPI type mapping for version %s", kubeadmAPIGroupVersion)
   116  	}
   117  
   118  	targetKubeadmObj = targetKubeadmObj.DeepCopyObject().(conversion.Convertible)
   119  	if err := targetKubeadmObj.ConvertFrom(obj); err != nil {
   120  		return "", errors.Wrapf(err, "failed to convert to KubeadmAPI type for version %s", kubeadmAPIGroupVersion)
   121  	}
   122  
   123  	codecs, err := getCodecsFor(kubeadmAPIGroupVersion, targetKubeadmObj)
   124  	if err != nil {
   125  		return "", err
   126  	}
   127  
   128  	yaml, err := toYaml(targetKubeadmObj, kubeadmAPIGroupVersion, codecs)
   129  	if err != nil {
   130  		return "", errors.Wrapf(err, "failed to generate yaml for the Kubeadm API for version %s", kubeadmAPIGroupVersion)
   131  	}
   132  	return string(yaml), nil
   133  }
   134  
   135  func getCodecsFor(gv schema.GroupVersion, obj runtime.Object) (serializer.CodecFactory, error) {
   136  	sb := &scheme.Builder{GroupVersion: gv}
   137  	sb.Register(obj)
   138  	kubeadmScheme, err := sb.Build()
   139  	if err != nil {
   140  		return serializer.CodecFactory{}, errors.Wrapf(err, "failed to build scheme for kubeadm types conversions")
   141  	}
   142  	return serializer.NewCodecFactory(kubeadmScheme), nil
   143  }
   144  
   145  func toYaml(obj runtime.Object, gv runtime.GroupVersioner, codecs serializer.CodecFactory) ([]byte, error) {
   146  	info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeYAML)
   147  	if !ok {
   148  		return []byte{}, errors.Errorf("unsupported media type %q", runtime.ContentTypeYAML)
   149  	}
   150  
   151  	encoder := codecs.EncoderForVersion(info.Serializer, gv)
   152  	return runtime.Encode(encoder, obj)
   153  }
   154  
   155  // UnmarshalClusterConfiguration tries to translate a Kubeadm API yaml back to the Cluster API ClusterConfiguration type.
   156  // NOTE: The yaml could be any of the known formats for the kubeadm ClusterConfiguration type.
   157  func UnmarshalClusterConfiguration(yaml string) (*bootstrapv1.ClusterConfiguration, error) {
   158  	obj := &bootstrapv1.ClusterConfiguration{}
   159  	if err := unmarshalFromVersions(yaml, clusterConfigurationVersionTypeMap, obj); err != nil {
   160  		return nil, err
   161  	}
   162  	return obj, nil
   163  }
   164  
   165  // UnmarshalClusterStatus tries to translate a Kubeadm API yaml back to the Cluster API ClusterStatus type.
   166  // NOTE: The yaml could be any of the known formats for the kubeadm ClusterStatus type.
   167  func UnmarshalClusterStatus(yaml string) (*bootstrapv1.ClusterStatus, error) {
   168  	obj := &bootstrapv1.ClusterStatus{}
   169  	if err := unmarshalFromVersions(yaml, clusterStatusVersionTypeMap, obj); err != nil {
   170  		return nil, err
   171  	}
   172  	return obj, nil
   173  }
   174  
   175  // UnmarshalInitConfiguration tries to translate a Kubeadm API yaml back to the InitConfiguration type.
   176  // NOTE: The yaml could be any of the known formats for the kubeadm InitConfiguration type.
   177  func UnmarshalInitConfiguration(yaml string) (*bootstrapv1.InitConfiguration, error) {
   178  	obj := &bootstrapv1.InitConfiguration{}
   179  	if err := unmarshalFromVersions(yaml, initConfigurationVersionTypeMap, obj); err != nil {
   180  		return nil, err
   181  	}
   182  	return obj, nil
   183  }
   184  
   185  // UnmarshalJoinConfiguration tries to translate a Kubeadm API yaml back to the JoinConfiguration type.
   186  // NOTE: The yaml could be any of the known formats for the kubeadm JoinConfiguration type.
   187  func UnmarshalJoinConfiguration(yaml string) (*bootstrapv1.JoinConfiguration, error) {
   188  	obj := &bootstrapv1.JoinConfiguration{}
   189  	if err := unmarshalFromVersions(yaml, joinConfigurationVersionTypeMap, obj); err != nil {
   190  		return nil, err
   191  	}
   192  	return obj, nil
   193  }
   194  
   195  func unmarshalFromVersions(yaml string, kubeadmAPIVersions map[schema.GroupVersion]conversion.Convertible, capiObj conversion.Hub) error {
   196  	// For each know kubeadm API version
   197  	for gv, obj := range kubeadmAPIVersions {
   198  		// Tries conversion from yaml to the corresponding kubeadmObj
   199  		kubeadmObj := obj.DeepCopyObject()
   200  		gvk := kubeadmObj.GetObjectKind().GroupVersionKind()
   201  		codecs, err := getCodecsFor(gv, kubeadmObj)
   202  		if err != nil {
   203  			return errors.Wrapf(err, "failed to build scheme for kubeadm types conversions")
   204  		}
   205  
   206  		_, _, err = codecs.UniversalDeserializer().Decode([]byte(yaml), &gvk, kubeadmObj)
   207  		if err == nil {
   208  			// If conversion worked, then converts the kubeadmObj (spoke) back to the Cluster API ClusterConfiguration type (hub).
   209  			if err := kubeadmObj.(conversion.Convertible).ConvertTo(capiObj); err != nil {
   210  				return errors.Wrapf(err, "failed to convert kubeadm types to Cluster API types")
   211  			}
   212  			return nil
   213  		}
   214  	}
   215  	return errors.New("unknown kubeadm types")
   216  }