...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs/configset.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package componentconfigs
    18  
    19  import (
    20  	"github.com/pkg/errors"
    21  
    22  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	clientset "k8s.io/client-go/kubernetes"
    28  	"k8s.io/klog/v2"
    29  
    30  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    31  	outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3"
    32  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    33  	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
    34  	"k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict"
    35  )
    36  
    37  // handler is a package internal type that handles component config factory and common functionality.
    38  // Every component config group should have exactly one static instance of handler.
    39  type handler struct {
    40  	// GroupVersion holds this handler's group name and preferred version
    41  	GroupVersion schema.GroupVersion
    42  
    43  	// AddToScheme points to a func that should add the GV types to a schema
    44  	AddToScheme func(*runtime.Scheme) error
    45  
    46  	// CreateEmpty returns an empty kubeadmapi.ComponentConfig (not even defaulted)
    47  	CreateEmpty func() kubeadmapi.ComponentConfig
    48  
    49  	// fromCluster should load the component config from a config map on the cluster.
    50  	// Don't use this directly! Use FromCluster instead!
    51  	fromCluster func(*handler, clientset.Interface, *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error)
    52  }
    53  
    54  // FromDocumentMap looks in the document map for documents with this handler's group.
    55  // If such are found a new component config is instantiated and the documents are loaded into it.
    56  // No error is returned if no documents are found.
    57  func (h *handler) FromDocumentMap(docmap kubeadmapi.DocumentMap) (kubeadmapi.ComponentConfig, error) {
    58  	for gvk := range docmap {
    59  		if gvk.Group == h.GroupVersion.Group {
    60  			cfg := h.CreateEmpty()
    61  			if err := cfg.Unmarshal(docmap); err != nil {
    62  				return nil, err
    63  			}
    64  			// consider all successfully loaded configs from a document map as user supplied
    65  			cfg.SetUserSupplied(true)
    66  			return cfg, nil
    67  		}
    68  	}
    69  	return nil, nil
    70  }
    71  
    72  // fromConfigMap is an utility function, which will load the value of a key of a config map and use h.FromDocumentMap() to perform the parsing
    73  // This is an utility func. Used by the component config support implementations. Don't use it outside of that context.
    74  func (h *handler) fromConfigMap(client clientset.Interface, cmName, cmKey string, mustExist bool) (kubeadmapi.ComponentConfig, error) {
    75  	configMap, err := apiclient.GetConfigMapWithShortRetry(client, metav1.NamespaceSystem, cmName)
    76  	if err != nil {
    77  		if !mustExist && (apierrors.IsNotFound(err) || apierrors.IsForbidden(err)) {
    78  			klog.Warningf("Warning: No %s config is loaded. Continuing without it: %v", h.GroupVersion, err)
    79  			return nil, nil
    80  		}
    81  		return nil, err
    82  	}
    83  
    84  	configData, ok := configMap.Data[cmKey]
    85  	if !ok {
    86  		return nil, errors.Errorf("unexpected error when reading %s ConfigMap: %s key value pair missing", cmName, cmKey)
    87  	}
    88  
    89  	gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(configData))
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	// If the checksum comes up neatly we assume the config was generated
    95  	generatedConfig := VerifyConfigMapSignature(configMap)
    96  
    97  	componentCfg, err := h.FromDocumentMap(gvkmap)
    98  	if err != nil {
    99  		// If the config was generated and we get UnsupportedConfigVersionError, we skip loading it.
   100  		// This will force us to use the generated default current version (effectively regenerating the config with the current version).
   101  		if _, ok := err.(*UnsupportedConfigVersionError); ok && generatedConfig {
   102  			return nil, nil
   103  		}
   104  		return nil, err
   105  	}
   106  
   107  	if componentCfg != nil {
   108  		componentCfg.SetUserSupplied(!generatedConfig)
   109  	}
   110  
   111  	return componentCfg, nil
   112  }
   113  
   114  // FromCluster loads a component from a config map in the cluster
   115  func (h *handler) FromCluster(clientset clientset.Interface, clusterCfg *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error) {
   116  	return h.fromCluster(h, clientset, clusterCfg)
   117  }
   118  
   119  // known holds the known component config handlers. Add new component configs here.
   120  var known = []*handler{
   121  	&kubeProxyHandler,
   122  	&kubeletHandler,
   123  }
   124  
   125  // configBase is the base type for all component config implementations
   126  type configBase struct {
   127  	// GroupVersion holds the supported GroupVersion for the inheriting config
   128  	GroupVersion schema.GroupVersion
   129  
   130  	// userSupplied tells us if the config is user supplied (invalid checksum) or not
   131  	userSupplied bool
   132  }
   133  
   134  func (cb *configBase) IsUserSupplied() bool {
   135  	return cb.userSupplied
   136  }
   137  
   138  func (cb *configBase) SetUserSupplied(userSupplied bool) {
   139  	cb.userSupplied = userSupplied
   140  }
   141  
   142  func (cb *configBase) DeepCopyInto(other *configBase) {
   143  	*other = *cb
   144  }
   145  
   146  func cloneBytes(in []byte) []byte {
   147  	out := make([]byte, len(in))
   148  	copy(out, in)
   149  	return out
   150  }
   151  
   152  // Marshal is an utility function, used by the component config support implementations to marshal a runtime.Object to YAML with the
   153  // correct group and version
   154  func (cb *configBase) Marshal(object runtime.Object) ([]byte, error) {
   155  	return kubeadmutil.MarshalToYamlForCodecs(object, cb.GroupVersion, Codecs)
   156  }
   157  
   158  // Unmarshal attempts to unmarshal a runtime.Object from a document map. If no object is found, no error is returned.
   159  // If a matching group is found, but no matching version an error is returned indicating that users should do manual conversion.
   160  func (cb *configBase) Unmarshal(from kubeadmapi.DocumentMap, into runtime.Object) error {
   161  	for gvk, yaml := range from {
   162  		// If this is a different group, we ignore it
   163  		if gvk.Group != cb.GroupVersion.Group {
   164  			continue
   165  		}
   166  
   167  		if gvk.Version != cb.GroupVersion.Version {
   168  			return &UnsupportedConfigVersionError{
   169  				OldVersion:     gvk.GroupVersion(),
   170  				CurrentVersion: cb.GroupVersion,
   171  				Document:       cloneBytes(yaml),
   172  			}
   173  		}
   174  
   175  		// Print warnings for strict errors
   176  		if err := strict.VerifyUnmarshalStrict([]*runtime.Scheme{Scheme}, gvk, yaml); err != nil {
   177  			klog.Warning(err.Error())
   178  		}
   179  
   180  		// As long as we support only component configs with a single kind, this is allowed
   181  		return runtime.DecodeInto(Codecs.UniversalDecoder(), yaml, into)
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // ensureInitializedComponentConfigs is an utility func to initialize the ComponentConfigMap in ClusterConfiguration prior to possible writes to it
   188  func ensureInitializedComponentConfigs(clusterCfg *kubeadmapi.ClusterConfiguration) {
   189  	if clusterCfg.ComponentConfigs == nil {
   190  		clusterCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{}
   191  	}
   192  }
   193  
   194  // Default sets up defaulted component configs in the supplied ClusterConfiguration
   195  func Default(clusterCfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint, nodeRegOpts *kubeadmapi.NodeRegistrationOptions) {
   196  	ensureInitializedComponentConfigs(clusterCfg)
   197  
   198  	for _, handler := range known {
   199  		// If the component config exists, simply default it. Otherwise, create it before defaulting.
   200  		group := handler.GroupVersion.Group
   201  		if componentCfg, ok := clusterCfg.ComponentConfigs[group]; ok {
   202  			componentCfg.Default(clusterCfg, localAPIEndpoint, nodeRegOpts)
   203  		} else {
   204  			componentCfg := handler.CreateEmpty()
   205  			componentCfg.Default(clusterCfg, localAPIEndpoint, nodeRegOpts)
   206  			clusterCfg.ComponentConfigs[group] = componentCfg
   207  		}
   208  	}
   209  }
   210  
   211  // FetchFromCluster attempts to fetch all known component configs from their config maps and store them in the supplied ClusterConfiguration
   212  func FetchFromCluster(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) error {
   213  	ensureInitializedComponentConfigs(clusterCfg)
   214  
   215  	for _, handler := range known {
   216  		componentCfg, err := handler.FromCluster(client, clusterCfg)
   217  		if err != nil {
   218  			return err
   219  		}
   220  
   221  		if componentCfg != nil {
   222  			clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg
   223  		}
   224  	}
   225  
   226  	return nil
   227  }
   228  
   229  // FetchFromDocumentMap attempts to load all known component configs from a document map into the supplied ClusterConfiguration
   230  func FetchFromDocumentMap(clusterCfg *kubeadmapi.ClusterConfiguration, docmap kubeadmapi.DocumentMap) error {
   231  	ensureInitializedComponentConfigs(clusterCfg)
   232  
   233  	for _, handler := range known {
   234  		componentCfg, err := handler.FromDocumentMap(docmap)
   235  		if err != nil {
   236  			return err
   237  		}
   238  
   239  		if componentCfg != nil {
   240  			clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // FetchFromClusterWithLocalOverwrites fetches component configs from a cluster and overwrites them locally with
   248  // the ones present in the supplied document map. If any UnsupportedConfigVersionError are not handled by the configs
   249  // in the document map, the function returns them all as a single UnsupportedConfigVersionsErrorMap.
   250  // This function is normally called only in some specific cases during upgrade.
   251  func FetchFromClusterWithLocalOverwrites(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, docmap kubeadmapi.DocumentMap) error {
   252  	ensureInitializedComponentConfigs(clusterCfg)
   253  
   254  	oldVersionErrs := UnsupportedConfigVersionsErrorMap{}
   255  
   256  	for _, handler := range known {
   257  		componentCfg, err := handler.FromCluster(client, clusterCfg)
   258  		if err != nil {
   259  			if vererr, ok := err.(*UnsupportedConfigVersionError); ok {
   260  				oldVersionErrs[handler.GroupVersion.Group] = vererr
   261  			} else {
   262  				return err
   263  			}
   264  		} else if componentCfg != nil {
   265  			clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg
   266  		}
   267  	}
   268  
   269  	for _, handler := range known {
   270  		componentCfg, err := handler.FromDocumentMap(docmap)
   271  		if err != nil {
   272  			if vererr, ok := err.(*UnsupportedConfigVersionError); ok {
   273  				oldVersionErrs[handler.GroupVersion.Group] = vererr
   274  			} else {
   275  				return err
   276  			}
   277  		} else if componentCfg != nil {
   278  			clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg
   279  			delete(oldVersionErrs, handler.GroupVersion.Group)
   280  		}
   281  	}
   282  
   283  	if len(oldVersionErrs) != 0 {
   284  		return oldVersionErrs
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // GetVersionStates returns a slice of ComponentConfigVersionState structs
   291  // describing all supported component config groups that were identified on the cluster
   292  func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) ([]outputapiv1alpha3.ComponentConfigVersionState, error) {
   293  	// We don't want to modify clusterCfg so we make a working deep copy of it.
   294  	// Also, we don't want the defaulted component configs so we get rid of them.
   295  	scratchClusterCfg := clusterCfg.DeepCopy()
   296  	scratchClusterCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{}
   297  
   298  	err := FetchFromCluster(scratchClusterCfg, client)
   299  	if err != nil {
   300  		// This seems to be a genuine error so we end here
   301  		return nil, err
   302  	}
   303  
   304  	results := []outputapiv1alpha3.ComponentConfigVersionState{}
   305  	for _, handler := range known {
   306  		group := handler.GroupVersion.Group
   307  		if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok {
   308  			// Normally loaded component config. No manual upgrade required on behalf of users.
   309  			results = append(results, outputapiv1alpha3.ComponentConfigVersionState{
   310  				Group:            group,
   311  				CurrentVersion:   handler.GroupVersion.Version, // Currently kubeadm supports only one version per API
   312  				PreferredVersion: handler.GroupVersion.Version, // group so we can get away with these being the same
   313  			})
   314  		} else {
   315  			// This config was either not present (user did not install an addon) or the config was unsupported kubeadm
   316  			// generated one and is therefore skipped so we can automatically re-generate it (no action required on
   317  			// behalf of the user).
   318  			results = append(results, outputapiv1alpha3.ComponentConfigVersionState{
   319  				Group:            group,
   320  				PreferredVersion: handler.GroupVersion.Version,
   321  			})
   322  		}
   323  	}
   324  
   325  	return results, nil
   326  }
   327  
   328  // Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration
   329  // TODO: investigate if the function can be repurposed for validating component config via CLI
   330  func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList {
   331  	return field.ErrorList{}
   332  }
   333  

View as plain text