mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
kubeadm: Handle config loading only in one place, and only use the internal version of the API internally. Fix bugs
This commit is contained in:
parent
d2952c0b2e
commit
f95e63cd10
@ -18,15 +18,13 @@ package v1alpha1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -101,35 +99,3 @@ func proxyFeatureListToMap(m map[string]interface{}) error {
|
|||||||
unstructured.SetNestedMap(m, gateMap, featureGatePath...)
|
unstructured.SetNestedMap(m, gateMap, featureGatePath...)
|
||||||
return nil
|
return 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)
|
|
||||||
}
|
|
||||||
return m2
|
|
||||||
case []interface{}:
|
|
||||||
for i, v := range x {
|
|
||||||
x[i] = convert(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
@ -17,37 +17,11 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const test196 = "testdata/kubeadm196.yaml"
|
|
||||||
|
|
||||||
func TestUpgrade(t *testing.T) {
|
|
||||||
testYAML, err := ioutil.ReadFile(test196)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't read test data: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := LoadYAML(testYAML)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't unmarshal test yaml: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
|
||||||
AddToScheme(scheme)
|
|
||||||
codecs := serializer.NewCodecFactory(scheme)
|
|
||||||
|
|
||||||
obj := &MasterConfiguration{}
|
|
||||||
if err := Migrate(decoded, obj, codecs); err != nil {
|
|
||||||
t.Fatalf("couldn't decode migrated object: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyFeatureListToMap(t *testing.T) {
|
func TestProxyFeatureListToMap(t *testing.T) {
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
@ -32,7 +32,6 @@ import (
|
|||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
@ -115,8 +114,8 @@ var (
|
|||||||
|
|
||||||
// NewCmdInit returns "kubeadm init" command.
|
// NewCmdInit returns "kubeadm init" command.
|
||||||
func NewCmdInit(out io.Writer) *cobra.Command {
|
func NewCmdInit(out io.Writer) *cobra.Command {
|
||||||
cfg := &kubeadmapiv1alpha1.MasterConfiguration{}
|
externalcfg := &kubeadmapiv1alpha1.MasterConfiguration{}
|
||||||
kubeadmscheme.Scheme.Default(cfg)
|
kubeadmscheme.Scheme.Default(externalcfg)
|
||||||
|
|
||||||
var cfgPath string
|
var cfgPath string
|
||||||
var skipPreFlight bool
|
var skipPreFlight bool
|
||||||
@ -130,26 +129,26 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
|||||||
Short: "Run this command in order to set up the Kubernetes master.",
|
Short: "Run this command in order to set up the Kubernetes master.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
kubeadmscheme.Scheme.Default(externalcfg)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
|
if externalcfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeadmscheme.Scheme.Default(cfg)
|
|
||||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
|
||||||
kubeadmscheme.Scheme.Convert(cfg, internalcfg, nil)
|
|
||||||
|
|
||||||
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
|
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
i, err := NewInit(cfgPath, internalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun)
|
err = validation.ValidateMixedArguments(cmd.Flags())
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
i, err := NewInit(cfgPath, externalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
kubeadmutil.CheckErr(i.Validate(cmd))
|
|
||||||
kubeadmutil.CheckErr(i.Run(out))
|
kubeadmutil.CheckErr(i.Run(out))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInitConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString)
|
AddInitConfigFlags(cmd.PersistentFlags(), externalcfg, &featureGatesString)
|
||||||
AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &ignorePreflightErrors)
|
AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &ignorePreflightErrors)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@ -239,27 +238,15 @@ func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight, sk
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewInit validates given arguments and instantiates Init struct with provided information.
|
// NewInit validates given arguments and instantiates Init struct with provided information.
|
||||||
func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, ignorePreflightErrors sets.String, skipTokenPrint, dryRun bool) (*Init, error) {
|
func NewInit(cfgPath string, externalcfg *kubeadmapiv1alpha1.MasterConfiguration, ignorePreflightErrors sets.String, skipTokenPrint, dryRun bool) (*Init, error) {
|
||||||
|
|
||||||
if cfgPath != "" {
|
// Either use the config file if specified, or convert the defaults in the external to an internal cfg representation
|
||||||
glog.V(1).Infof("[init] reading config file from: " + cfgPath)
|
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, externalcfg)
|
||||||
b, err := ioutil.ReadFile(cfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
|
|
||||||
}
|
|
||||||
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), b, cfg); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set defaults dynamically that the API group defaulting can't (by fetching information from the internet, looking up network interfaces, etc.)
|
|
||||||
glog.V(1).Infof("[init] setting dynamic defaults")
|
|
||||||
err := configutil.SetInitDynamicDefaults(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(1).Infof("[init] validating Kubernetes version")
|
glog.V(1).Infof("[init] validating feature gates")
|
||||||
if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil {
|
if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -293,14 +280,6 @@ type Init struct {
|
|||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates configuration passed to "kubeadm init"
|
|
||||||
func (i *Init) Validate(cmd *cobra.Command) error {
|
|
||||||
if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return validation.ValidateMasterConfiguration(i.cfg).ToAggregate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes master node provisioning, including certificates, needed static pod manifests, etc.
|
// Run executes master node provisioning, including certificates, needed static pod manifests, etc.
|
||||||
func (i *Init) Run(out io.Writer) error {
|
func (i *Init) Run(out io.Writer) error {
|
||||||
// Get directories to write files to; can be faked if we're dry-running
|
// Get directories to write files to; can be faked if we're dry-running
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
|
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
@ -37,7 +38,6 @@ import (
|
|||||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
||||||
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
|
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
|
||||||
"k8s.io/kubernetes/pkg/util/version"
|
"k8s.io/kubernetes/pkg/util/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
|
|||||||
// If the version is specified in config file, pick up that value.
|
// If the version is specified in config file, pick up that value.
|
||||||
if flags.parent.cfgPath != "" {
|
if flags.parent.cfgPath != "" {
|
||||||
glog.V(1).Infof("fetching configuration from file", flags.parent.cfgPath)
|
glog.V(1).Infof("fetching configuration from file", flags.parent.cfgPath)
|
||||||
cfg, err := upgrade.FetchConfigurationFromFile(flags.parent.cfgPath)
|
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(parentFlags.cfgPath, &kubeadmapiv1alpha1.MasterConfiguration{})
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
if cfg.KubernetesVersion != "" {
|
if cfg.KubernetesVersion != "" {
|
||||||
@ -147,26 +147,21 @@ func RunApply(flags *applyFlags) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab the external, versioned configuration and convert it to the internal type for usage here later
|
|
||||||
glog.V(1).Infof("[upgrade/apply] converting configuration for internal use")
|
|
||||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
|
||||||
legacyscheme.Scheme.Convert(upgradeVars.cfg, internalcfg, nil)
|
|
||||||
|
|
||||||
// Validate requested and validate actual version
|
// Validate requested and validate actual version
|
||||||
glog.V(1).Infof("[upgrade/apply] validating requested and actual version")
|
glog.V(1).Infof("[upgrade/apply] validating requested and actual version")
|
||||||
if err := configutil.NormalizeKubernetesVersion(internalcfg); err != nil {
|
if err := configutil.NormalizeKubernetesVersion(upgradeVars.cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use normalized version string in all following code.
|
// Use normalized version string in all following code.
|
||||||
flags.newK8sVersionStr = internalcfg.KubernetesVersion
|
flags.newK8sVersionStr = upgradeVars.cfg.KubernetesVersion
|
||||||
k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
|
k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to parse normalized version %q as a semantic version", flags.newK8sVersionStr)
|
return fmt.Errorf("unable to parse normalized version %q as a semantic version", flags.newK8sVersionStr)
|
||||||
}
|
}
|
||||||
flags.newK8sVersion = k8sVer
|
flags.newK8sVersion = k8sVer
|
||||||
|
|
||||||
if err := features.ValidateVersion(features.InitFeatureGates, internalcfg.FeatureGates, internalcfg.KubernetesVersion); err != nil {
|
if err := features.ValidateVersion(features.InitFeatureGates, upgradeVars.cfg.FeatureGates, upgradeVars.cfg.KubernetesVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,18 +181,18 @@ func RunApply(flags *applyFlags) error {
|
|||||||
// Use a prepuller implementation based on creating DaemonSets
|
// Use a prepuller implementation based on creating DaemonSets
|
||||||
// and block until all DaemonSets are ready; then we know for sure that all control plane images are cached locally
|
// and block until all DaemonSets are ready; then we know for sure that all control plane images are cached locally
|
||||||
glog.V(1).Infof("[upgrade/apply] creating prepuller")
|
glog.V(1).Infof("[upgrade/apply] creating prepuller")
|
||||||
prepuller := upgrade.NewDaemonSetPrepuller(upgradeVars.client, upgradeVars.waiter, internalcfg)
|
prepuller := upgrade.NewDaemonSetPrepuller(upgradeVars.client, upgradeVars.waiter, upgradeVars.cfg)
|
||||||
upgrade.PrepullImagesInParallel(prepuller, flags.imagePullTimeout)
|
upgrade.PrepullImagesInParallel(prepuller, flags.imagePullTimeout)
|
||||||
|
|
||||||
// Now; perform the upgrade procedure
|
// Now; perform the upgrade procedure
|
||||||
glog.V(1).Infof("[upgrade/apply] performing upgrade")
|
glog.V(1).Infof("[upgrade/apply] performing upgrade")
|
||||||
if err := PerformControlPlaneUpgrade(flags, upgradeVars.client, upgradeVars.waiter, internalcfg); err != nil {
|
if err := PerformControlPlaneUpgrade(flags, upgradeVars.client, upgradeVars.waiter, upgradeVars.cfg); err != nil {
|
||||||
return fmt.Errorf("[upgrade/apply] FATAL: %v", err)
|
return fmt.Errorf("[upgrade/apply] FATAL: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade RBAC rules and addons.
|
// Upgrade RBAC rules and addons.
|
||||||
glog.V(1).Infof("[upgrade/postupgrade] upgrading RBAC rules and addons")
|
glog.V(1).Infof("[upgrade/postupgrade] upgrading RBAC rules and addons")
|
||||||
if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, internalcfg, flags.newK8sVersion, flags.dryRun); err != nil {
|
if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, upgradeVars.cfg, flags.newK8sVersion, flags.dryRun); err != nil {
|
||||||
return fmt.Errorf("[upgrade/postupgrade] FATAL post-upgrade error: %v", err)
|
return fmt.Errorf("[upgrade/postupgrade] FATAL post-upgrade error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
fakediscovery "k8s.io/client-go/discovery/fake"
|
fakediscovery "k8s.io/client-go/discovery/fake"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
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"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
@ -42,7 +43,7 @@ import (
|
|||||||
// TODO - Restructure or rename upgradeVariables
|
// TODO - Restructure or rename upgradeVariables
|
||||||
type upgradeVariables struct {
|
type upgradeVariables struct {
|
||||||
client clientset.Interface
|
client clientset.Interface
|
||||||
cfg *kubeadmapiv1alpha1.MasterConfiguration
|
cfg *kubeadmapi.MasterConfiguration
|
||||||
versionGetter upgrade.VersionGetter
|
versionGetter upgrade.VersionGetter
|
||||||
waiter apiclient.Waiter
|
waiter apiclient.Waiter
|
||||||
}
|
}
|
||||||
@ -94,13 +95,16 @@ func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// printConfiguration prints the external version of the API to yaml
|
// printConfiguration prints the external version of the API to yaml
|
||||||
func printConfiguration(cfg *kubeadmapiv1alpha1.MasterConfiguration, w io.Writer) {
|
func printConfiguration(cfg *kubeadmapi.MasterConfiguration, w io.Writer) {
|
||||||
// Short-circuit if cfg is nil, so we can safely get the value of the pointer below
|
// Short-circuit if cfg is nil, so we can safely get the value of the pointer below
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgYaml, err := kubeadmutil.MarshalToYamlForCodecs(cfg, kubeadmapiv1alpha1.SchemeGroupVersion, kubeadmscheme.Codecs)
|
externalcfg := &kubeadmapiv1alpha1.MasterConfiguration{}
|
||||||
|
kubeadmscheme.Scheme.Convert(cfg, externalcfg, nil)
|
||||||
|
|
||||||
|
cfgYaml, err := kubeadmutil.MarshalToYamlForCodecs(externalcfg, kubeadmapiv1alpha1.SchemeGroupVersion, kubeadmscheme.Codecs)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Fprintln(w, "[upgrade/config] Configuration used:")
|
fmt.Fprintln(w, "[upgrade/config] Configuration used:")
|
||||||
|
|
||||||
|
@ -20,12 +20,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrintConfiguration(t *testing.T) {
|
func TestPrintConfiguration(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
cfg *kubeadmapiv1alpha1.MasterConfiguration
|
cfg *kubeadmapi.MasterConfiguration
|
||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
expectedBytes []byte
|
expectedBytes []byte
|
||||||
}{
|
}{
|
||||||
@ -34,7 +34,7 @@ func TestPrintConfiguration(t *testing.T) {
|
|||||||
expectedBytes: []byte(""),
|
expectedBytes: []byte(""),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: &kubeadmapiv1alpha1.MasterConfiguration{
|
cfg: &kubeadmapi.MasterConfiguration{
|
||||||
KubernetesVersion: "v1.7.1",
|
KubernetesVersion: "v1.7.1",
|
||||||
},
|
},
|
||||||
expectedBytes: []byte(`[upgrade/config] Configuration used:
|
expectedBytes: []byte(`[upgrade/config] Configuration used:
|
||||||
@ -71,9 +71,9 @@ func TestPrintConfiguration(t *testing.T) {
|
|||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: &kubeadmapiv1alpha1.MasterConfiguration{
|
cfg: &kubeadmapi.MasterConfiguration{
|
||||||
KubernetesVersion: "v1.7.1",
|
KubernetesVersion: "v1.7.1",
|
||||||
Networking: kubeadmapiv1alpha1.Networking{
|
Networking: kubeadmapi.Networking{
|
||||||
ServiceSubnet: "10.96.0.1/12",
|
ServiceSubnet: "10.96.0.1/12",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -111,10 +111,10 @@ func TestPrintConfiguration(t *testing.T) {
|
|||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: &kubeadmapiv1alpha1.MasterConfiguration{
|
cfg: &kubeadmapi.MasterConfiguration{
|
||||||
KubernetesVersion: "v1.7.1",
|
KubernetesVersion: "v1.7.1",
|
||||||
Etcd: kubeadmapiv1alpha1.Etcd{
|
Etcd: kubeadmapi.Etcd{
|
||||||
SelfHosted: &kubeadmapiv1alpha1.SelfHostedEtcd{
|
SelfHosted: &kubeadmapi.SelfHostedEtcd{
|
||||||
CertificatesDir: "/var/foo",
|
CertificatesDir: "/var/foo",
|
||||||
ClusterServiceName: "foo",
|
ClusterServiceName: "foo",
|
||||||
EtcdVersion: "v0.1.0",
|
EtcdVersion: "v0.1.0",
|
||||||
|
@ -26,11 +26,13 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
|
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
|
|||||||
// If the version is specified in config file, pick up that value.
|
// If the version is specified in config file, pick up that value.
|
||||||
if parentFlags.cfgPath != "" {
|
if parentFlags.cfgPath != "" {
|
||||||
glog.V(1).Infof("fetching configuration from file", parentFlags.cfgPath)
|
glog.V(1).Infof("fetching configuration from file", parentFlags.cfgPath)
|
||||||
cfg, err := upgrade.FetchConfigurationFromFile(parentFlags.cfgPath)
|
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(parentFlags.cfgPath, &kubeadmapiv1alpha1.MasterConfiguration{})
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
if cfg.KubernetesVersion != "" {
|
if cfg.KubernetesVersion != "" {
|
||||||
|
@ -25,15 +25,12 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
|
||||||
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FetchConfiguration fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster
|
// FetchConfiguration fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster
|
||||||
func FetchConfiguration(client clientset.Interface, w io.Writer, cfgPath string) (*kubeadmapiv1alpha1.MasterConfiguration, error) {
|
func FetchConfiguration(client clientset.Interface, w io.Writer, cfgPath string) (*kubeadmapi.MasterConfiguration, error) {
|
||||||
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
|
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
|
||||||
|
|
||||||
// Load the configuration from a file or the cluster
|
// Load the configuration from a file or the cluster
|
||||||
@ -42,26 +39,8 @@ func FetchConfiguration(client clientset.Interface, w io.Writer, cfgPath string)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the versioned configuration populated from the configmap, default it and validate
|
// Take the versioned configuration populated from the file or configmap, convert it to internal, default and validate
|
||||||
// Return the internal version of the API object
|
versionedcfg, err := configutil.BytesToInternalConfig(configBytes)
|
||||||
versionedcfg, err := bytesToValidatedMasterConfig(configBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not decode configuration: %v", err)
|
|
||||||
}
|
|
||||||
return versionedcfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchConfigurationFromFile fetch configuration from a file
|
|
||||||
func FetchConfigurationFromFile(cfgPath string) (*kubeadmapiv1alpha1.MasterConfiguration, error) {
|
|
||||||
// Load the configuration from a file or the cluster
|
|
||||||
configBytes, err := ioutil.ReadFile(cfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the versioned configuration populated from the configmap, default it and validate
|
|
||||||
// Return the internal version of the API object
|
|
||||||
versionedcfg, err := bytesToValidatedMasterConfig(configBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not decode configuration: %v", err)
|
return nil, fmt.Errorf("could not decode configuration: %v", err)
|
||||||
}
|
}
|
||||||
@ -70,6 +49,7 @@ func FetchConfigurationFromFile(cfgPath string) (*kubeadmapiv1alpha1.MasterConfi
|
|||||||
|
|
||||||
// loadConfigurationBytes loads the configuration byte slice from either a file or the cluster ConfigMap
|
// loadConfigurationBytes loads the configuration byte slice from either a file or the cluster ConfigMap
|
||||||
func loadConfigurationBytes(client clientset.Interface, w io.Writer, cfgPath string) ([]byte, error) {
|
func loadConfigurationBytes(client clientset.Interface, w io.Writer, cfgPath string) ([]byte, error) {
|
||||||
|
// The config file has the highest priority
|
||||||
if cfgPath != "" {
|
if cfgPath != "" {
|
||||||
fmt.Printf("[upgrade/config] Reading configuration options from a file: %s\n", cfgPath)
|
fmt.Printf("[upgrade/config] Reading configuration options from a file: %s\n", cfgPath)
|
||||||
return ioutil.ReadFile(cfgPath)
|
return ioutil.ReadFile(cfgPath)
|
||||||
@ -95,34 +75,3 @@ func loadConfigurationBytes(client clientset.Interface, w io.Writer, cfgPath str
|
|||||||
fmt.Printf("[upgrade/config] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", metav1.NamespaceSystem, constants.MasterConfigurationConfigMap)
|
fmt.Printf("[upgrade/config] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", metav1.NamespaceSystem, constants.MasterConfigurationConfigMap)
|
||||||
return []byte(configMap.Data[constants.MasterConfigurationConfigMapKey]), nil
|
return []byte(configMap.Data[constants.MasterConfigurationConfigMapKey]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// bytesToValidatedMasterConfig converts a byte array to an external, defaulted and validated configuration object
|
|
||||||
func bytesToValidatedMasterConfig(b []byte) (*kubeadmapiv1alpha1.MasterConfiguration, error) {
|
|
||||||
cfg := &kubeadmapiv1alpha1.MasterConfiguration{}
|
|
||||||
finalCfg := &kubeadmapiv1alpha1.MasterConfiguration{}
|
|
||||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
|
||||||
|
|
||||||
decoded, err := kubeadmapiv1alpha1.LoadYAML(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode config from bytes: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := kubeadmapiv1alpha1.Migrate(decoded, cfg, kubeadmscheme.Codecs); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to migrate config from previous version: %v", err)
|
|
||||||
}
|
|
||||||
// Default and convert to the internal version
|
|
||||||
kubeadmscheme.Scheme.Default(cfg)
|
|
||||||
kubeadmscheme.Scheme.Convert(cfg, internalcfg, nil)
|
|
||||||
|
|
||||||
// Applies dynamic defaults to settings not provided with flags
|
|
||||||
if err := configutil.SetInitDynamicDefaults(internalcfg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Validates cfg (flags/configs + defaults + dynamic defaults)
|
|
||||||
if err := validation.ValidateMasterConfiguration(internalcfg).ToAggregate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Finally converts back to the external version
|
|
||||||
kubeadmscheme.Scheme.Convert(internalcfg, finalCfg, nil)
|
|
||||||
return finalCfg, nil
|
|
||||||
}
|
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
netutil "k8s.io/apimachinery/pkg/util/net"
|
netutil "k8s.io/apimachinery/pkg/util/net"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
@ -76,52 +75,89 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryLoadMasterConfiguration tries to loads a Master configuration from the given file (if defined)
|
|
||||||
func TryLoadMasterConfiguration(cfgPath string, cfg *kubeadmapiv1alpha1.MasterConfiguration) error {
|
|
||||||
|
|
||||||
if cfgPath != "" {
|
|
||||||
b, err := ioutil.ReadFile(cfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
|
|
||||||
}
|
|
||||||
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), b, cfg); err != nil {
|
|
||||||
return fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigFileAndDefaultsToInternalConfig takes a path to a config file and a versioned configuration that can serve as the default config
|
// ConfigFileAndDefaultsToInternalConfig takes a path to a config file and a versioned configuration that can serve as the default config
|
||||||
// If cfgPath is specified, defaultversionedcfg will always get overridden. Otherwise, the default config (often populated by flags) will be used.
|
// If cfgPath is specified, defaultversionedcfg will always get overridden. Otherwise, the default config (often populated by flags) will be used.
|
||||||
// Then the external, versioned configuration is defaulted and converted to the internal type.
|
// Then the external, versioned configuration is defaulted and converted to the internal type.
|
||||||
// Right thereafter, the configuration is defaulted again with dynamic values (like IP addresses of a machine, etc)
|
// Right thereafter, the configuration is defaulted again with dynamic values (like IP addresses of a machine, etc)
|
||||||
// Lastly, the internal config is validated and returned.
|
// Lastly, the internal config is validated and returned.
|
||||||
func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1alpha1.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
|
func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1alpha1.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
|
||||||
glog.V(1).Infoln("configuring files and defaults to internal config")
|
|
||||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||||
|
|
||||||
|
if cfgPath != "" {
|
||||||
// Loads configuration from config file, if provided
|
// Loads configuration from config file, if provided
|
||||||
// Nb. --config overrides command line flags
|
// Nb. --config overrides command line flags
|
||||||
glog.V(1).Infoln("attempting to load configuration from config file")
|
glog.V(1).Infoln("loading configuration from the given file")
|
||||||
if err := TryLoadMasterConfiguration(cfgPath, defaultversionedcfg); err != nil {
|
|
||||||
return nil, err
|
b, err := ioutil.ReadFile(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
|
||||||
|
}
|
||||||
|
return BytesToInternalConfig(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
|
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
|
||||||
// static default values to cfg only for values not provided with flags
|
// static default values to cfg only for values not provided with flags
|
||||||
kubeadmscheme.Scheme.Default(defaultversionedcfg)
|
kubeadmscheme.Scheme.Default(defaultversionedcfg)
|
||||||
kubeadmscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
|
kubeadmscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
|
||||||
// Applies dynamic defaults to settings not provided with flags
|
|
||||||
if err := SetInitDynamicDefaults(internalcfg); err != nil {
|
return defaultAndValidate(internalcfg)
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
// BytesToInternalConfig converts a byte array to an internal, defaulted and validated configuration object
|
||||||
|
func BytesToInternalConfig(b []byte) (*kubeadmapi.MasterConfiguration, error) {
|
||||||
|
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||||
|
|
||||||
|
decoded, err := kubeadmutil.LoadYAML(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode config from bytes: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates cfg (flags/configs + defaults + dynamic defaults)
|
// As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information
|
||||||
if err := validation.ValidateMasterConfiguration(internalcfg).ToAggregate(); err != nil {
|
// we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug,
|
||||||
|
// it could only write
|
||||||
|
// TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for
|
||||||
|
// v1alpha1 in v1.12
|
||||||
|
kind := decoded["kind"]
|
||||||
|
apiVersion := decoded["apiVersion"]
|
||||||
|
if kind == nil || len(kind.(string)) == 0 {
|
||||||
|
decoded["kind"] = "MasterConfiguration"
|
||||||
|
}
|
||||||
|
if apiVersion == nil || len(apiVersion.(string)) == 0 {
|
||||||
|
decoded["apiVersion"] = kubeadmapiv1alpha1.SchemeGroupVersion.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Between v1.9 and v1.10 the proxy componentconfig in the v1alpha1 MasterConfiguration changed unexpectedly, which broke unmarshalling out-of-the-box
|
||||||
|
// Hence, we need to workaround this bug in the v1alpha1 API
|
||||||
|
if decoded["apiVersion"] == kubeadmapiv1alpha1.SchemeGroupVersion.String() {
|
||||||
|
v1alpha1cfg := &kubeadmapiv1alpha1.MasterConfiguration{}
|
||||||
|
if err := kubeadmapiv1alpha1.Migrate(decoded, v1alpha1cfg, kubeadmscheme.Codecs); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to migrate config from previous version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default and convert to the internal version
|
||||||
|
kubeadmscheme.Scheme.Default(v1alpha1cfg)
|
||||||
|
kubeadmscheme.Scheme.Convert(v1alpha1cfg, internalcfg, nil)
|
||||||
|
} else {
|
||||||
|
// TODO: Add support for an upcoming v1alpha2 API
|
||||||
|
// TODO: In the future, we can unmarshal any two or more external types into the internal object directly using the following syntax.
|
||||||
|
// Long-term we don't need this if/else clause. In the future this will do
|
||||||
|
// runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmapiv2alpha3.SchemeGroupVersion), b, internalcfg)
|
||||||
|
return nil, fmt.Errorf("unknown API version for kubeadm configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultAndValidate(internalcfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultAndValidate(cfg *kubeadmapi.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
|
||||||
|
// Applies dynamic defaults to settings not provided with flags
|
||||||
|
if err := SetInitDynamicDefaults(cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return internalcfg, nil
|
// Validates cfg (flags/configs + defaults + dynamic defaults)
|
||||||
|
if err := validation.ValidateMasterConfiguration(cfg).ToAggregate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeKubernetesVersion resolves version labels, sets alternative
|
// NormalizeKubernetesVersion resolves version labels, sets alternative
|
||||||
|
@ -17,8 +17,11 @@ limitations under the License.
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
@ -58,3 +61,35 @@ func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs se
|
|||||||
decoder := codecs.DecoderToVersion(info.Serializer, gv)
|
decoder := codecs.DecoderToVersion(info.Serializer, gv)
|
||||||
return runtime.Decode(decoder, buffer)
|
return runtime.Decode(decoder, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
return m2
|
||||||
|
case []interface{}:
|
||||||
|
for i, v := range x {
|
||||||
|
x[i] = convert(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user