mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	Refactor a bit of the config YAML loading code, and support loading multiple YAML documents
This commit is contained in:
		| @@ -17,6 +17,7 @@ limitations under the License. | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| @@ -28,6 +29,7 @@ import ( | ||||
| 	flag "github.com/spf13/pflag" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	clientset "k8s.io/client-go/kubernetes" | ||||
| 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" | ||||
| 	kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" | ||||
| @@ -44,14 +46,8 @@ import ( | ||||
| 	utilsexec "k8s.io/utils/exec" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// TODO: Figure out how to get these constants from the API machinery | ||||
| 	masterConfig = "MasterConfiguration" | ||||
| 	nodeConfig   = "NodeConfiguration" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	availableAPIObjects = []string{masterConfig, nodeConfig} | ||||
| 	availableAPIObjects = []string{constants.MasterConfigurationKind, constants.NodeConfigurationKind} | ||||
| 	// sillyToken is only set statically to make kubeadm not randomize the token on every run | ||||
| 	sillyToken = kubeadmapiv1alpha3.BootstrapToken{ | ||||
| 		Token: &kubeadmapiv1alpha3.BootstrapTokenString{ | ||||
| @@ -110,16 +106,13 @@ func NewCmdConfigPrintDefault(out io.Writer) *cobra.Command { | ||||
| 			if len(apiObjects) == 0 { | ||||
| 				apiObjects = availableAPIObjects | ||||
| 			} | ||||
| 			for i, apiObject := range apiObjects { | ||||
| 				if i > 0 { | ||||
| 					fmt.Fprintln(out, "---") | ||||
| 				} | ||||
|  | ||||
| 			allBytes := [][]byte{} | ||||
| 			for _, apiObject := range apiObjects { | ||||
| 				cfgBytes, err := getDefaultAPIObjectBytes(apiObject) | ||||
| 				kubeadmutil.CheckErr(err) | ||||
| 				// Print the API object byte array | ||||
| 				fmt.Fprintf(out, "%s", cfgBytes) | ||||
| 				allBytes = append(allBytes, cfgBytes) | ||||
| 			} | ||||
| 			fmt.Fprint(out, string(bytes.Join(allBytes, []byte(constants.YAMLDocumentSeparator)))) | ||||
| 		}, | ||||
| 	} | ||||
| 	cmd.Flags().StringSliceVar(&apiObjects, "api-objects", apiObjects, | ||||
| @@ -128,26 +121,29 @@ func NewCmdConfigPrintDefault(out io.Writer) *cobra.Command { | ||||
| } | ||||
|  | ||||
| func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) { | ||||
| 	if apiObject == masterConfig { | ||||
|  | ||||
| 		internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha3.MasterConfiguration{ | ||||
| 			BootstrapTokens: []kubeadmapiv1alpha3.BootstrapToken{sillyToken}, | ||||
| 	var internalcfg runtime.Object | ||||
| 	var err error | ||||
| 	switch apiObject { | ||||
| 	case constants.MasterConfigurationKind: | ||||
| 		internalcfg, err = configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha3.MasterConfiguration{ | ||||
| 			API:               kubeadmapiv1alpha3.API{AdvertiseAddress: "1.2.3.4"}, | ||||
| 			BootstrapTokens:   []kubeadmapiv1alpha3.BootstrapToken{sillyToken}, | ||||
| 			KubernetesVersion: fmt.Sprintf("v1.%d.0", constants.MinimumControlPlaneVersion.Minor()+1), | ||||
| 		}) | ||||
| 		kubeadmutil.CheckErr(err) | ||||
|  | ||||
| 		return kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha3.SchemeGroupVersion, kubeadmscheme.Codecs) | ||||
| 	} | ||||
| 	if apiObject == nodeConfig { | ||||
| 		internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha3.NodeConfiguration{ | ||||
| 	case constants.NodeConfigurationKind: | ||||
| 		internalcfg, err = configutil.NodeConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha3.NodeConfiguration{ | ||||
| 			Token: sillyToken.Token.String(), | ||||
| 			DiscoveryTokenAPIServers:               []string{"kube-apiserver:6443"}, | ||||
| 			DiscoveryTokenUnsafeSkipCAVerification: true, | ||||
| 		}) | ||||
| 		kubeadmutil.CheckErr(err) | ||||
|  | ||||
| 		return kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha3.SchemeGroupVersion, kubeadmscheme.Codecs) | ||||
| 		// TODO: DiscoveryTokenUnsafeSkipCAVerification: true needs to be set for validation to pass, but shouldn't be recommended as the default | ||||
| 	default: | ||||
| 		err = fmt.Errorf("--api-object needs to be one of %v", availableAPIObjects) | ||||
| 	} | ||||
| 	return []byte{}, fmt.Errorf("--api-object needs to be one of %v", availableAPIObjects) | ||||
| 	if err != nil { | ||||
| 		return []byte{}, err | ||||
| 	} | ||||
| 	return kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha3.SchemeGroupVersion, kubeadmscheme.Codecs) | ||||
| } | ||||
|  | ||||
| // NewCmdConfigMigrate returns cobra.Command for "kubeadm config migrate" command | ||||
| @@ -176,31 +172,12 @@ func NewCmdConfigMigrate(out io.Writer) *cobra.Command { | ||||
| 				kubeadmutil.CheckErr(fmt.Errorf("The --old-config flag is mandatory")) | ||||
| 			} | ||||
|  | ||||
| 			b, err := ioutil.ReadFile(oldCfgPath) | ||||
| 			internalcfg, err := configutil.AnyConfigFileAndDefaultsToInternal(oldCfgPath) | ||||
| 			kubeadmutil.CheckErr(err) | ||||
|  | ||||
| 			var outputBytes []byte | ||||
| 			gvk, err := kubeadmutil.GroupVersionKindFromBytes(b, kubeadmscheme.Codecs) | ||||
| 			outputBytes, err := kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha3.SchemeGroupVersion, kubeadmscheme.Codecs) | ||||
| 			kubeadmutil.CheckErr(err) | ||||
|  | ||||
| 			switch gvk.Kind { | ||||
| 			case masterConfig: | ||||
| 				internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha3.MasterConfiguration{}) | ||||
| 				kubeadmutil.CheckErr(err) | ||||
|  | ||||
| 				outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha3.SchemeGroupVersion, kubeadmscheme.Codecs) | ||||
| 				kubeadmutil.CheckErr(err) | ||||
| 			case nodeConfig: | ||||
| 				internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha3.NodeConfiguration{}) | ||||
| 				kubeadmutil.CheckErr(err) | ||||
|  | ||||
| 				// TODO: In the future we might not want to duplicate these two lines of code for every case here. | ||||
| 				outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha3.SchemeGroupVersion, kubeadmscheme.Codecs) | ||||
| 				kubeadmutil.CheckErr(err) | ||||
| 			default: | ||||
| 				kubeadmutil.CheckErr(fmt.Errorf("Didn't recognize type with GroupVersionKind: %v", gvk)) | ||||
| 			} | ||||
|  | ||||
| 			if newCfgPath == "" { | ||||
| 				fmt.Fprint(out, string(outputBytes)) | ||||
| 			} else { | ||||
|   | ||||
| @@ -18,12 +18,10 @@ package phases | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" | ||||
| 	kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" | ||||
| 	kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" | ||||
| 	cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" | ||||
| @@ -38,12 +36,6 @@ import ( | ||||
| 	utilsexec "k8s.io/utils/exec" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// TODO: Figure out how to get these constants from the API machinery | ||||
| 	masterConfig = "MasterConfiguration" | ||||
| 	nodeConfig   = "NodeConfiguration" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	kubeletWriteEnvFileLongDesc = normalizer.LongDesc(` | ||||
| 		Writes an environment file with flags that should be passed to the kubelet executing on the master or node. | ||||
| @@ -56,7 +48,7 @@ var ( | ||||
| 		kubeadm alpha phase kubelet write-env-file --config masterconfig.yaml | ||||
|  | ||||
| 		# Writes a dynamic environment file with kubelet flags from a NodeConfiguration file. | ||||
| 		kubeadm alpha phase kubelet write-env-file --config nodeConfig.yaml | ||||
| 		kubeadm alpha phase kubelet write-env-file --config nodeconfig.yaml | ||||
| 		`) | ||||
|  | ||||
| 	kubeletConfigUploadLongDesc = normalizer.LongDesc(` | ||||
| @@ -144,12 +136,7 @@ func NewCmdKubeletWriteEnvFile() *cobra.Command { | ||||
|  | ||||
| // RunKubeletWriteEnvFile is the function that is run when "kubeadm phase kubelet write-env-file" is executed | ||||
| func RunKubeletWriteEnvFile(cfgPath string) error { | ||||
| 	b, err := ioutil.ReadFile(cfgPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	gvk, err := kubeadmutil.GroupVersionKindFromBytes(b, kubeadmscheme.Codecs) | ||||
| 	internalcfg, err := configutil.AnyConfigFileAndDefaultsToInternal(cfgPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -157,30 +144,18 @@ func RunKubeletWriteEnvFile(cfgPath string) error { | ||||
| 	var nodeRegistrationObj *kubeadmapi.NodeRegistrationOptions | ||||
| 	var featureGates map[string]bool | ||||
| 	var registerWithTaints bool | ||||
| 	switch gvk.Kind { | ||||
| 	case masterConfig: | ||||
| 		internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha3.MasterConfiguration{}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		nodeRegistrationObj = &internalcfg.NodeRegistration | ||||
| 		featureGates = internalcfg.FeatureGates | ||||
|  | ||||
| 	switch cfg := internalcfg.(type) { | ||||
| 	case *kubeadmapi.MasterConfiguration: | ||||
| 		nodeRegistrationObj = &cfg.NodeRegistration | ||||
| 		featureGates = cfg.FeatureGates | ||||
| 		registerWithTaints = false | ||||
| 	case nodeConfig: | ||||
| 		internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha3.NodeConfiguration{}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		nodeRegistrationObj = &internalcfg.NodeRegistration | ||||
| 		featureGates = internalcfg.FeatureGates | ||||
| 	case *kubeadmapi.NodeConfiguration: | ||||
| 		nodeRegistrationObj = &cfg.NodeRegistration | ||||
| 		featureGates = cfg.FeatureGates | ||||
| 		registerWithTaints = true | ||||
| 	default: | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Didn't recognize type with GroupVersionKind: %v", gvk) | ||||
| 		} | ||||
| 	} | ||||
| 	if nodeRegistrationObj == nil { | ||||
| 		return fmt.Errorf("couldn't load nodeRegistration field from config file") | ||||
| 		return fmt.Errorf("couldn't read config file, no matching kind found") | ||||
| 	} | ||||
|  | ||||
| 	if err := kubeletphase.WriteKubeletDynamicEnvFile(nodeRegistrationObj, featureGates, registerWithTaints, constants.KubeletRunDirectory); err != nil { | ||||
|   | ||||
| @@ -285,6 +285,16 @@ const ( | ||||
|  | ||||
| 	// CoreDNSVersion is the version of CoreDNS to be deployed if it is used | ||||
| 	CoreDNSVersion = "1.1.3" | ||||
|  | ||||
| 	// MasterConfigurationKind is the string kind value for the MasterConfiguration struct | ||||
| 	MasterConfigurationKind = "MasterConfiguration" | ||||
|  | ||||
| 	// NodeConfigurationKind is the string kind value for the MasterConfiguration struct | ||||
| 	NodeConfigurationKind = "NodeConfiguration" | ||||
|  | ||||
| 	// YAMLDocumentSeparator is the separator for YAML documents | ||||
| 	// TODO: Find a better place for this constant | ||||
| 	YAMLDocumentSeparator = "---\n" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
|   | ||||
							
								
								
									
										118
									
								
								cmd/kubeadm/app/util/config/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								cmd/kubeadm/app/util/config/common.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| /* | ||||
| Copyright 2018 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 config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" | ||||
| 	kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||
| 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||
| 	"k8s.io/kubernetes/pkg/util/version" | ||||
| ) | ||||
|  | ||||
| // AnyConfigFileAndDefaultsToInternal reads either a MasterConfiguration or NodeConfiguration and unmarshals it | ||||
| func AnyConfigFileAndDefaultsToInternal(cfgPath string) (runtime.Object, error) { | ||||
| 	b, err := ioutil.ReadFile(cfgPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// First, check if the gvk list has MasterConfiguration and in that case try to unmarshal it | ||||
| 	if kubeadmutil.GroupVersionKindsHasMasterConfiguration(gvks) { | ||||
| 		return ConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha3.MasterConfiguration{}) | ||||
| 	} | ||||
| 	if kubeadmutil.GroupVersionKindsHasNodeConfiguration(gvks) { | ||||
| 		return NodeConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha3.NodeConfiguration{}) | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("didn't recognize types with GroupVersionKind: %v", gvks) | ||||
| } | ||||
|  | ||||
| // DetectUnsupportedVersion reads YAML bytes, extracts the TypeMeta information and errors out with an user-friendly message if the API spec is too old for this kubeadm version | ||||
| func DetectUnsupportedVersion(b []byte) error { | ||||
| 	gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// TODO: On our way to making the kubeadm API beta and higher, give good user output in case they use an old config file with a new kubeadm version, and | ||||
| 	// tell them how to upgrade. The support matrix will look something like this now and in the future: | ||||
| 	// v1.10 and earlier: v1alpha1 | ||||
| 	// v1.11: v1alpha1 read-only, writes only v1alpha2 config | ||||
| 	// v1.12: v1alpha2 read-only, writes only v1beta1 config. Warns if the user tries to use v1alpha1 | ||||
| 	// v1.13 and v1.14: v1beta1 read-only, writes only v1 config. Warns if the user tries to use v1alpha1 or v1alpha2. | ||||
| 	// v1.15: v1 is the only supported format. | ||||
| 	oldKnownAPIVersions := map[string]string{ | ||||
| 		"kubeadm.k8s.io/v1alpha1": "v1.11", | ||||
| 	} | ||||
| 	// If we find an old API version in this gvk list, error out and tell the user why this doesn't work | ||||
| 	for _, gvk := range gvks { | ||||
| 		if useKubeadmVersion := oldKnownAPIVersions[gvk.GroupVersion().String()]; len(useKubeadmVersion) != 0 { | ||||
| 			return fmt.Errorf("your configuration file uses an old API spec: %q. Please use kubeadm %s instead and run 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gvk.GroupVersion().String(), useKubeadmVersion) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NormalizeKubernetesVersion resolves version labels, sets alternative | ||||
| // image registry if requested for CI builds, and validates minimal | ||||
| // version that kubeadm SetInitDynamicDefaultssupports. | ||||
| func NormalizeKubernetesVersion(cfg *kubeadmapi.MasterConfiguration) error { | ||||
| 	// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images | ||||
| 	if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) { | ||||
| 		cfg.CIImageRepository = constants.DefaultCIImageRepository | ||||
| 	} | ||||
|  | ||||
| 	// Parse and validate the version argument and resolve possible CI version labels | ||||
| 	ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	cfg.KubernetesVersion = ver | ||||
|  | ||||
| 	// Parse the given kubernetes version and make sure it's higher than the lowest supported | ||||
| 	k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err) | ||||
| 	} | ||||
| 	if k8sVersion.LessThan(constants.MinimumControlPlaneVersion) { | ||||
| 		return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", constants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // LowercaseSANs can be used to force all SANs to be lowercase so it passes IsDNS1123Subdomain | ||||
| func LowercaseSANs(sans []string) { | ||||
| 	for i, san := range sans { | ||||
| 		lowercase := strings.ToLower(san) | ||||
| 		if lowercase != san { | ||||
| 			glog.V(1).Infof("lowercasing SAN %q to %q", san, lowercase) | ||||
| 			sans[i] = lowercase | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										158
									
								
								cmd/kubeadm/app/util/config/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								cmd/kubeadm/app/util/config/common_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| /* | ||||
| Copyright 2018 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 config | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"testing" | ||||
|  | ||||
| 	kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||
| ) | ||||
|  | ||||
| var files = map[string][]byte{ | ||||
| 	"Master_v1alpha1": []byte(` | ||||
| apiVersion: kubeadm.k8s.io/v1alpha1 | ||||
| kind: MasterConfiguration | ||||
| `), | ||||
| 	"Node_v1alpha1": []byte(` | ||||
| apiVersion: kubeadm.k8s.io/v1alpha1 | ||||
| kind: NodeConfiguration | ||||
| `), | ||||
| 	"Master_v1alpha3": []byte(` | ||||
| apiVersion: kubeadm.k8s.io/v1alpha3 | ||||
| kind: MasterConfiguration | ||||
| `), | ||||
| 	"Node_v1alpha3": []byte(` | ||||
| apiVersion: kubeadm.k8s.io/v1alpha3 | ||||
| kind: NodeConfiguration | ||||
| `), | ||||
| 	"NoKind": []byte(` | ||||
| apiVersion: baz.k8s.io/v1 | ||||
| foo: foo | ||||
| bar: bar | ||||
| `), | ||||
| 	"NoAPIVersion": []byte(` | ||||
| kind: Bar | ||||
| foo: foo | ||||
| bar: bar | ||||
| `), | ||||
| } | ||||
|  | ||||
| func TestDetectUnsupportedVersion(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		name         string | ||||
| 		fileContents []byte | ||||
| 		expectedErr  bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "Master_v1alpha1", | ||||
| 			fileContents: files["Master_v1alpha1"], | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Node_v1alpha1", | ||||
| 			fileContents: files["Node_v1alpha1"], | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Master_v1alpha3", | ||||
| 			fileContents: files["Master_v1alpha3"], | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Node_v1alpha3", | ||||
| 			fileContents: files["Node_v1alpha3"], | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "DuplicateMaster", | ||||
| 			fileContents: bytes.Join([][]byte{files["Master_v1alpha3"], files["Master_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "NoKind", | ||||
| 			fileContents: files["NoKind"], | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "NoAPIVersion", | ||||
| 			fileContents: files["NoAPIVersion"], | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "v1alpha1InMultiple", | ||||
| 			fileContents: bytes.Join([][]byte{files["Master_v1alpha3"], files["Master_v1alpha1"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, rt := range tests { | ||||
| 		t.Run(rt.name, func(t2 *testing.T) { | ||||
|  | ||||
| 			err := DetectUnsupportedVersion(rt.fileContents) | ||||
| 			if (err != nil) != rt.expectedErr { | ||||
| 				t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestLowercaseSANs(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name string | ||||
| 		in   []string | ||||
| 		out  []string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "empty struct", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "already lowercase", | ||||
| 			in:   []string{"example.k8s.io"}, | ||||
| 			out:  []string{"example.k8s.io"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "ip addresses and uppercase", | ||||
| 			in:   []string{"EXAMPLE.k8s.io", "10.100.0.1"}, | ||||
| 			out:  []string{"example.k8s.io", "10.100.0.1"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "punycode and uppercase", | ||||
| 			in:   []string{"xn--7gq663byk9a.xn--fiqz9s", "ANOTHEREXAMPLE.k8s.io"}, | ||||
| 			out:  []string{"xn--7gq663byk9a.xn--fiqz9s", "anotherexample.k8s.io"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range tests { | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			cfg := &kubeadmapiv1alpha3.MasterConfiguration{ | ||||
| 				APIServerCertSANs: test.in, | ||||
| 			} | ||||
|  | ||||
| 			LowercaseSANs(cfg.APIServerCertSANs) | ||||
|  | ||||
| 			if len(cfg.APIServerCertSANs) != len(test.out) { | ||||
| 				t.Fatalf("expected %d elements, got %d", len(test.out), len(cfg.APIServerCertSANs)) | ||||
| 			} | ||||
|  | ||||
| 			for i, expected := range test.out { | ||||
| 				if cfg.APIServerCertSANs[i] != expected { | ||||
| 					t.Errorf("expected element %d to be %q, got %q", i, expected, cfg.APIServerCertSANs[i]) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -20,7 +20,6 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
|  | ||||
| @@ -33,9 +32,7 @@ import ( | ||||
| 	kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" | ||||
| 	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||
| 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||
| 	"k8s.io/kubernetes/pkg/util/node" | ||||
| 	"k8s.io/kubernetes/pkg/util/version" | ||||
| 	nodeutil "k8s.io/kubernetes/pkg/util/node" | ||||
| ) | ||||
|  | ||||
| // SetInitDynamicDefaults checks and sets configuration values for the MasterConfiguration object | ||||
| @@ -88,7 +85,7 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { | ||||
| 		cfg.BootstrapTokens[i].Token = token | ||||
| 	} | ||||
|  | ||||
| 	cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name) | ||||
| 	cfg.NodeRegistration.Name = nodeutil.GetHostname(cfg.NodeRegistration.Name) | ||||
|  | ||||
| 	// Only if the slice is nil, we should append the master taint. This allows the user to specify an empty slice for no default master taint | ||||
| 	if cfg.NodeRegistration.Taints == nil { | ||||
| @@ -152,64 +149,3 @@ func defaultAndValidate(cfg *kubeadmapi.MasterConfiguration) (*kubeadmapi.Master | ||||
| 	} | ||||
| 	return cfg, nil | ||||
| } | ||||
|  | ||||
| // DetectUnsupportedVersion reads YAML bytes, extracts the TypeMeta information and errors out with an user-friendly message if the API spec is too old for this kubeadm version | ||||
| func DetectUnsupportedVersion(b []byte) error { | ||||
| 	apiVersionStr, _, err := kubeadmutil.ExtractAPIVersionAndKindFromYAML(b) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// TODO: On our way to making the kubeadm API beta and higher, give good user output in case they use an old config file with a new kubeadm version, and | ||||
| 	// tell them how to upgrade. The support matrix will look something like this now and in the future: | ||||
| 	// v1.10 and earlier: v1alpha1 | ||||
| 	// v1.11: v1alpha1 read-only, writes only v1alpha2 config | ||||
| 	// v1.12: v1alpha2 read-only, writes only v1beta1 config. Warns if the user tries to use v1alpha1 | ||||
| 	// v1.13 and v1.14: v1beta1 read-only, writes only v1 config. Warns if the user tries to use v1alpha1 or v1alpha2. | ||||
| 	// v1.15: v1 is the only supported format. | ||||
| 	oldKnownAPIVersions := map[string]string{ | ||||
| 		"kubeadm.k8s.io/v1alpha1": "v1.11", | ||||
| 	} | ||||
| 	if useKubeadmVersion := oldKnownAPIVersions[apiVersionStr]; len(useKubeadmVersion) != 0 { | ||||
| 		return fmt.Errorf("your configuration file seem to use an old API spec. Please use kubeadm %s instead and run 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", useKubeadmVersion) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NormalizeKubernetesVersion resolves version labels, sets alternative | ||||
| // image registry if requested for CI builds, and validates minimal | ||||
| // version that kubeadm supports. | ||||
| func NormalizeKubernetesVersion(cfg *kubeadmapi.MasterConfiguration) error { | ||||
| 	// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images | ||||
| 	if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) { | ||||
| 		cfg.CIImageRepository = kubeadmconstants.DefaultCIImageRepository | ||||
| 	} | ||||
|  | ||||
| 	// Parse and validate the version argument and resolve possible CI version labels | ||||
| 	ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	cfg.KubernetesVersion = ver | ||||
|  | ||||
| 	// Parse the given kubernetes version and make sure it's higher than the lowest supported | ||||
| 	k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err) | ||||
| 	} | ||||
| 	if k8sVersion.LessThan(kubeadmconstants.MinimumControlPlaneVersion) { | ||||
| 		return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", kubeadmconstants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // LowercaseSANs can be used to force all SANs to be lowercase so it passes IsDNS1123Subdomain | ||||
| func LowercaseSANs(sans []string) { | ||||
| 	for i, san := range sans { | ||||
| 		lowercase := strings.ToLower(san) | ||||
| 		if lowercase != san { | ||||
| 			glog.V(1).Infof("lowercasing SAN %q to %q", san, lowercase) | ||||
| 			sans[i] = lowercase | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -127,50 +127,3 @@ func TestConfigFileAndDefaultsToInternalConfig(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestLowercaseSANs(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name string | ||||
| 		in   []string | ||||
| 		out  []string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "empty struct", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "already lowercase", | ||||
| 			in:   []string{"example.k8s.io"}, | ||||
| 			out:  []string{"example.k8s.io"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "ip addresses and uppercase", | ||||
| 			in:   []string{"EXAMPLE.k8s.io", "10.100.0.1"}, | ||||
| 			out:  []string{"example.k8s.io", "10.100.0.1"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "punycode and uppercase", | ||||
| 			in:   []string{"xn--7gq663byk9a.xn--fiqz9s", "ANOTHEREXAMPLE.k8s.io"}, | ||||
| 			out:  []string{"xn--7gq663byk9a.xn--fiqz9s", "anotherexample.k8s.io"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range tests { | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			cfg := &kubeadmapiv1alpha3.MasterConfiguration{ | ||||
| 				APIServerCertSANs: test.in, | ||||
| 			} | ||||
|  | ||||
| 			LowercaseSANs(cfg.APIServerCertSANs) | ||||
|  | ||||
| 			if len(cfg.APIServerCertSANs) != len(test.out) { | ||||
| 				t.Fatalf("expected %d elements, got %d", len(test.out), len(cfg.APIServerCertSANs)) | ||||
| 			} | ||||
|  | ||||
| 			for i, expected := range test.out { | ||||
| 				if cfg.APIServerCertSANs[i] != expected { | ||||
| 					t.Errorf("expected element %d to be %q, got %q", i, expected, cfg.APIServerCertSANs[i]) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -17,15 +17,20 @@ limitations under the License. | ||||
| package util | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
|  | ||||
| 	yaml "gopkg.in/yaml.v2" | ||||
| 	"github.com/ghodss/yaml" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/serializer" | ||||
| 	"k8s.io/apimachinery/pkg/util/errors" | ||||
| 	utilyaml "k8s.io/apimachinery/pkg/util/yaml" | ||||
| 	clientsetscheme "k8s.io/client-go/kubernetes/scheme" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||
| ) | ||||
|  | ||||
| // MarshalToYaml marshals an object into yaml. | ||||
| @@ -66,67 +71,88 @@ func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs se | ||||
| 	return runtime.Decode(decoder, buffer) | ||||
| } | ||||
|  | ||||
| // ExtractAPIVersionAndKindFromYAML extracts the APIVersion and Kind fields from YAML bytes | ||||
| func ExtractAPIVersionAndKindFromYAML(b []byte) (string, string, error) { | ||||
| 	decoded, err := LoadYAML(b) | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("unable to decode config from bytes: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	kindStr, ok := decoded["kind"].(string) | ||||
| 	if !ok || len(kindStr) == 0 { | ||||
| 		return "", "", fmt.Errorf("any config file must have the kind field set") | ||||
| 	} | ||||
| 	apiVersionStr, ok := decoded["apiVersion"].(string) | ||||
| 	if !ok || len(apiVersionStr) == 0 { | ||||
| 		return "", "", fmt.Errorf("any config file must have the apiVersion field set") | ||||
| 	} | ||||
| 	return apiVersionStr, kindStr, nil | ||||
| } | ||||
|  | ||||
| // GroupVersionKindFromBytes parses the bytes and returns the gvk | ||||
| // TODO: Find a better way to do this, invoking the API machinery directly without first loading the yaml manually | ||||
| func GroupVersionKindFromBytes(b []byte, codecs serializer.CodecFactory) (schema.GroupVersionKind, error) { | ||||
| 	apiVersionStr, kindStr, err := ExtractAPIVersionAndKindFromYAML(b) | ||||
| 	if err != nil { | ||||
| 		return schema.EmptyObjectKind.GroupVersionKind(), err | ||||
| 	} | ||||
|  | ||||
| 	gv, err := schema.ParseGroupVersion(apiVersionStr) | ||||
| 	if err != nil { | ||||
| 		return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to parse apiVersion: %v", err) | ||||
| 	} | ||||
| 	return gv.WithKind(kindStr), nil | ||||
| } | ||||
|  | ||||
| // LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}. | ||||
| func LoadYAML(bytes []byte) (map[string]interface{}, error) { | ||||
| 	var decoded map[interface{}]interface{} | ||||
| 	if err := yaml.Unmarshal(bytes, &decoded); err != nil { | ||||
| 		return map[string]interface{}{}, fmt.Errorf("couldn't unmarshal YAML: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	converted, ok := convert(decoded).(map[string]interface{}) | ||||
| 	if !ok { | ||||
| 		return map[string]interface{}{}, errors.New("yaml is not a map") | ||||
| 	} | ||||
|  | ||||
| 	return converted, nil | ||||
| } | ||||
|  | ||||
| // https://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang | ||||
| func convert(i interface{}) interface{} { | ||||
| 	switch x := i.(type) { | ||||
| 	case map[interface{}]interface{}: | ||||
| 		m2 := map[string]interface{}{} | ||||
| 		for k, v := range x { | ||||
| 			m2[k.(string)] = convert(v) | ||||
| // SplitYAMLDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document | ||||
| // and returns a map between the GroupVersionKind of the document and the document bytes | ||||
| func SplitYAMLDocuments(yamlBytes []byte) (map[schema.GroupVersionKind][]byte, error) { | ||||
| 	gvkmap := map[schema.GroupVersionKind][]byte{} | ||||
| 	knownKinds := map[string]bool{} | ||||
| 	errs := []error{} | ||||
| 	buf := bytes.NewBuffer(yamlBytes) | ||||
| 	reader := utilyaml.NewYAMLReader(bufio.NewReader(buf)) | ||||
| 	for { | ||||
| 		typeMetaInfo := runtime.TypeMeta{} | ||||
| 		// Read one YAML document at a time, until io.EOF is returned | ||||
| 		b, err := reader.Read() | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} else if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return m2 | ||||
| 	case []interface{}: | ||||
| 		for i, v := range x { | ||||
| 			x[i] = convert(v) | ||||
| 		if len(b) == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 		// Deserialize the TypeMeta information of this byte slice | ||||
| 		if err := yaml.Unmarshal(b, &typeMetaInfo); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		// Require TypeMeta information to be present | ||||
| 		if len(typeMetaInfo.APIVersion) == 0 || len(typeMetaInfo.Kind) == 0 { | ||||
| 			errs = append(errs, fmt.Errorf("invalid configuration: kind and apiVersion is mandatory information that needs to be specified in all YAML documents")) | ||||
| 			continue | ||||
| 		} | ||||
| 		// Check whether the kind has been registered before. If it has, throw an error | ||||
| 		if known := knownKinds[typeMetaInfo.Kind]; known { | ||||
| 			errs = append(errs, fmt.Errorf("invalid configuration: kind %q is specified twice in YAML file", typeMetaInfo.Kind)) | ||||
| 			continue | ||||
| 		} | ||||
| 		knownKinds[typeMetaInfo.Kind] = true | ||||
|  | ||||
| 		// Build a GroupVersionKind object from the deserialized TypeMeta object | ||||
| 		gv, err := schema.ParseGroupVersion(typeMetaInfo.APIVersion) | ||||
| 		if err != nil { | ||||
| 			errs = append(errs, fmt.Errorf("unable to parse apiVersion: %v", err)) | ||||
| 			continue | ||||
| 		} | ||||
| 		gvk := gv.WithKind(typeMetaInfo.Kind) | ||||
|  | ||||
| 		// Save the mapping between the gvk and the bytes that object consists of | ||||
| 		gvkmap[gvk] = b | ||||
| 	} | ||||
| 	if err := errors.NewAggregate(errs); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return gvkmap, nil | ||||
| } | ||||
|  | ||||
| // GroupVersionKindsFromBytes parses the bytes and returns a gvk slice | ||||
| func GroupVersionKindsFromBytes(b []byte) ([]schema.GroupVersionKind, error) { | ||||
| 	gvkmap, err := SplitYAMLDocuments(b) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	gvks := []schema.GroupVersionKind{} | ||||
| 	for gvk := range gvkmap { | ||||
| 		gvks = append(gvks, gvk) | ||||
| 	} | ||||
| 	return gvks, nil | ||||
| } | ||||
|  | ||||
| // GroupVersionKindsHasKind returns whether the following gvk slice contains the kind given as a parameter | ||||
| func GroupVersionKindsHasKind(gvks []schema.GroupVersionKind, kind string) bool { | ||||
| 	for _, gvk := range gvks { | ||||
| 		if gvk.Kind == kind { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return i | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // GroupVersionKindsHasMasterConfiguration returns whether the following gvk slice contains a MasterConfiguration object | ||||
| func GroupVersionKindsHasMasterConfiguration(gvks []schema.GroupVersionKind) bool { | ||||
| 	return GroupVersionKindsHasKind(gvks, constants.MasterConfigurationKind) | ||||
| } | ||||
|  | ||||
| // GroupVersionKindsHasNodeConfiguration returns whether the following gvk slice contains a NodeConfiguration object | ||||
| func GroupVersionKindsHasNodeConfiguration(gvks []schema.GroupVersionKind) bool { | ||||
| 	return GroupVersionKindsHasKind(gvks, constants.NodeConfigurationKind) | ||||
| } | ||||
|   | ||||
| @@ -17,15 +17,48 @@ limitations under the License. | ||||
| package util | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"testing" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" | ||||
| 	kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" | ||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||
| ) | ||||
|  | ||||
| var files = map[string][]byte{ | ||||
| 	"foo": []byte(` | ||||
| kind: Foo | ||||
| apiVersion: foo.k8s.io/v1 | ||||
| fooField: foo | ||||
| `), | ||||
| 	"bar": []byte(` | ||||
| apiVersion: bar.k8s.io/v2 | ||||
| barField: bar | ||||
| kind: Bar | ||||
| `), | ||||
| 	"baz": []byte(` | ||||
| apiVersion: baz.k8s.io/v1 | ||||
| kind: Baz | ||||
| baz: | ||||
| 	foo: bar | ||||
| `), | ||||
| 	"nokind": []byte(` | ||||
| apiVersion: baz.k8s.io/v1 | ||||
| foo: foo | ||||
| bar: bar | ||||
| `), | ||||
| 	"noapiversion": []byte(` | ||||
| kind: Bar | ||||
| foo: foo | ||||
| bar: bar | ||||
| `), | ||||
| } | ||||
|  | ||||
| func TestMarshalUnmarshalYaml(t *testing.T) { | ||||
| 	pod := &corev1.Pod{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| @@ -114,3 +147,248 @@ func TestMarshalUnmarshalToYamlForCodecs(t *testing.T) { | ||||
| 		t.Errorf("expected %v, got %v", *cfg, *cfg2) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSplitYAMLDocuments(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		name         string | ||||
| 		fileContents []byte | ||||
| 		gvkmap       map[schema.GroupVersionKind][]byte | ||||
| 		expectedErr  bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "FooOnly", | ||||
| 			fileContents: files["foo"], | ||||
| 			gvkmap: map[schema.GroupVersionKind][]byte{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: files["foo"], | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "FooBar", | ||||
| 			fileContents: bytes.Join([][]byte{files["foo"], files["bar"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			gvkmap: map[schema.GroupVersionKind][]byte{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: files["foo"], | ||||
| 				{Group: "bar.k8s.io", Version: "v2", Kind: "Bar"}: files["bar"], | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "FooTwiceInvalid", | ||||
| 			fileContents: bytes.Join([][]byte{files["foo"], files["bar"], files["foo"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "InvalidBaz", | ||||
| 			fileContents: bytes.Join([][]byte{files["foo"], files["baz"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "InvalidNoKind", | ||||
| 			fileContents: files["nokind"], | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "InvalidNoAPIVersion", | ||||
| 			fileContents: files["noapiversion"], | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, rt := range tests { | ||||
| 		t.Run(rt.name, func(t2 *testing.T) { | ||||
|  | ||||
| 			gvkmap, err := SplitYAMLDocuments(rt.fileContents) | ||||
| 			if (err != nil) != rt.expectedErr { | ||||
| 				t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil) | ||||
| 			} | ||||
|  | ||||
| 			if !reflect.DeepEqual(gvkmap, rt.gvkmap) { | ||||
| 				t2.Errorf("expected gvkmap: %s\n\tactual: %s\n", rt.gvkmap, gvkmap) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGroupVersionKindsFromBytes(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		name         string | ||||
| 		fileContents []byte | ||||
| 		gvks         []string | ||||
| 		expectedErr  bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "FooOnly", | ||||
| 			fileContents: files["foo"], | ||||
| 			gvks: []string{ | ||||
| 				"foo.k8s.io/v1, Kind=Foo", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "FooBar", | ||||
| 			fileContents: bytes.Join([][]byte{files["foo"], files["bar"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			gvks: []string{ | ||||
| 				"foo.k8s.io/v1, Kind=Foo", | ||||
| 				"bar.k8s.io/v2, Kind=Bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "FooTwiceInvalid", | ||||
| 			fileContents: bytes.Join([][]byte{files["foo"], files["bar"], files["foo"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			gvks:         []string{}, | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "InvalidBaz", | ||||
| 			fileContents: bytes.Join([][]byte{files["foo"], files["baz"]}, []byte(constants.YAMLDocumentSeparator)), | ||||
| 			gvks:         []string{}, | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "InvalidNoKind", | ||||
| 			fileContents: files["nokind"], | ||||
| 			gvks:         []string{}, | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "InvalidNoAPIVersion", | ||||
| 			fileContents: files["noapiversion"], | ||||
| 			gvks:         []string{}, | ||||
| 			expectedErr:  true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, rt := range tests { | ||||
| 		t.Run(rt.name, func(t2 *testing.T) { | ||||
|  | ||||
| 			gvks, err := GroupVersionKindsFromBytes(rt.fileContents) | ||||
| 			if (err != nil) != rt.expectedErr { | ||||
| 				t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil) | ||||
| 			} | ||||
|  | ||||
| 			strgvks := []string{} | ||||
| 			for _, gvk := range gvks { | ||||
| 				strgvks = append(strgvks, gvk.String()) | ||||
| 			} | ||||
| 			sort.Strings(strgvks) | ||||
| 			sort.Strings(rt.gvks) | ||||
|  | ||||
| 			if !reflect.DeepEqual(strgvks, rt.gvks) { | ||||
| 				t2.Errorf("expected gvks: %s\n\tactual: %s\n", rt.gvks, strgvks) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGroupVersionKindsHasKind(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		name     string | ||||
| 		gvks     []schema.GroupVersionKind | ||||
| 		kind     string | ||||
| 		expected bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "FooOnly", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 			}, | ||||
| 			kind:     "Foo", | ||||
| 			expected: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "FooBar", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 				{Group: "bar.k8s.io", Version: "v2", Kind: "Bar"}, | ||||
| 			}, | ||||
| 			kind:     "Bar", | ||||
| 			expected: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "FooBazNoBaz", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 				{Group: "bar.k8s.io", Version: "v2", Kind: "Bar"}, | ||||
| 			}, | ||||
| 			kind:     "Baz", | ||||
| 			expected: false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, rt := range tests { | ||||
| 		t.Run(rt.name, func(t2 *testing.T) { | ||||
|  | ||||
| 			actual := GroupVersionKindsHasKind(rt.gvks, rt.kind) | ||||
| 			if rt.expected != actual { | ||||
| 				t2.Errorf("expected gvks has kind: %t\n\tactual: %t\n", rt.expected, actual) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGroupVersionKindsHasMasterConfiguration(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		name     string | ||||
| 		gvks     []schema.GroupVersionKind | ||||
| 		kind     string | ||||
| 		expected bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "NoMasterConfiguration", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 			}, | ||||
| 			expected: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "MasterConfigurationFound", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 				{Group: "bar.k8s.io", Version: "v2", Kind: "MasterConfiguration"}, | ||||
| 			}, | ||||
| 			expected: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, rt := range tests { | ||||
| 		t.Run(rt.name, func(t2 *testing.T) { | ||||
|  | ||||
| 			actual := GroupVersionKindsHasMasterConfiguration(rt.gvks) | ||||
| 			if rt.expected != actual { | ||||
| 				t2.Errorf("expected gvks has MasterConfiguration: %t\n\tactual: %t\n", rt.expected, actual) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGroupVersionKindsHasNodeConfiguration(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		name     string | ||||
| 		gvks     []schema.GroupVersionKind | ||||
| 		kind     string | ||||
| 		expected bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "NoNodeConfiguration", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 			}, | ||||
| 			expected: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "NodeConfigurationFound", | ||||
| 			gvks: []schema.GroupVersionKind{ | ||||
| 				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, | ||||
| 				{Group: "bar.k8s.io", Version: "v2", Kind: "NodeConfiguration"}, | ||||
| 			}, | ||||
| 			expected: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, rt := range tests { | ||||
| 		t.Run(rt.name, func(t2 *testing.T) { | ||||
|  | ||||
| 			actual := GroupVersionKindsHasNodeConfiguration(rt.gvks) | ||||
| 			if rt.expected != actual { | ||||
| 				t2.Errorf("expected gvks has NodeConfiguration: %t\n\tactual: %t\n", rt.expected, actual) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user