mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #65631 from luxas/kubeadm_support_yaml_documents
Automatic merge from submit-queue (batch tested with PRs 65822, 65834, 65859, 65631). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. kubeadm: Add support for reading multiple YAML documents **What this PR does / why we need it**: In preparation for splitting the kubelet and kube-proxy componentconfigs out of the MasterConfiguration API struct, add support for reading multiple YAML documents **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: ref: kubernetes/kubeadm#911 Depends on: - [x] https://github.com/kubernetes/kubernetes/pull/65776 - [x] https://github.com/kubernetes/kubernetes/pull/65628 - [x] https://github.com/kubernetes/kubernetes/pull/65629 **Special notes for your reviewer**: Please only review the `Refactor a bit of the config YAML loading code, and support loading multiple YAML documents` commit **Release note**: ```release-note NONE ``` @kubernetes/sig-cluster-lifecycle-pr-reviews
This commit is contained in:
commit
d10ff1a205
@ -57,6 +57,7 @@ go_library(
|
||||
"//pkg/version:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/duration:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
|
@ -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 (
|
||||
|
@ -21,14 +21,16 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
)
|
||||
@ -49,8 +51,10 @@ go_test(
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha3:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -10,6 +10,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cluster.go",
|
||||
"common.go",
|
||||
"masterconfig.go",
|
||||
"nodeconfig.go",
|
||||
],
|
||||
@ -37,6 +38,7 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"common_test.go",
|
||||
"masterconfig_test.go",
|
||||
"nodeconfig_test.go",
|
||||
],
|
||||
@ -46,6 +48,7 @@ go_test(
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha3:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
|
||||
|
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user