k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/componentconfigs/configset.go (about)

     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  // GetVersionStates returns a slice of ComponentConfigVersionState structs
   248  // describing all supported component config groups that were identified on the cluster
   249  func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) ([]outputapiv1alpha3.ComponentConfigVersionState, error) {
   250  	// We don't want to modify clusterCfg so we make a working deep copy of it.
   251  	// Also, we don't want the defaulted component configs so we get rid of them.
   252  	scratchClusterCfg := clusterCfg.DeepCopy()
   253  	scratchClusterCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{}
   254  
   255  	err := FetchFromCluster(scratchClusterCfg, client)
   256  	if err != nil {
   257  		// This seems to be a genuine error so we end here
   258  		return nil, err
   259  	}
   260  
   261  	results := []outputapiv1alpha3.ComponentConfigVersionState{}
   262  	for _, handler := range known {
   263  		group := handler.GroupVersion.Group
   264  		if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok {
   265  			// Normally loaded component config. No manual upgrade required on behalf of users.
   266  			results = append(results, outputapiv1alpha3.ComponentConfigVersionState{
   267  				Group:            group,
   268  				CurrentVersion:   handler.GroupVersion.Version, // Currently kubeadm supports only one version per API
   269  				PreferredVersion: handler.GroupVersion.Version, // group so we can get away with these being the same
   270  			})
   271  		} else {
   272  			// This config was either not present (user did not install an addon) or the config was unsupported kubeadm
   273  			// generated one and is therefore skipped so we can automatically re-generate it (no action required on
   274  			// behalf of the user).
   275  			results = append(results, outputapiv1alpha3.ComponentConfigVersionState{
   276  				Group:            group,
   277  				PreferredVersion: handler.GroupVersion.Version,
   278  			})
   279  		}
   280  	}
   281  
   282  	return results, nil
   283  }
   284  
   285  // Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration
   286  // TODO: investigate if the function can be repurposed for validating component config via CLI
   287  func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList {
   288  	return field.ErrorList{}
   289  }