mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Merge pull request #88124 from rosti/kubeadm-cc-upgrade-plan
kubeadm upgrade plan: print a component config state table
This commit is contained in:
commit
a2aaae2dd5
@ -48,6 +48,26 @@ type ComponentUpgradePlan struct {
|
||||
NewVersion string
|
||||
}
|
||||
|
||||
// ComponentConfigVersionState describes the current and desired version of a component config
|
||||
type ComponentConfigVersionState struct {
|
||||
// Group points to the Kubernetes API group that covers the config
|
||||
Group string
|
||||
|
||||
// CurrentVersion is the currently active component config version
|
||||
// NOTE: This can be empty in case the config was not found on the cluster or it was unsupported
|
||||
// kubeadm generated version
|
||||
CurrentVersion string
|
||||
|
||||
// PreferredVersion is the component config version that is currently preferred by kubeadm for use.
|
||||
// NOTE: As of today, this is the only version supported by kubeadm.
|
||||
PreferredVersion string
|
||||
|
||||
// ManualUpgradeRequired indicates if users need to manually upgrade their component config versions. This happens if
|
||||
// the CurrentVersion of the config is user supplied (or modified) and no longer supported. Users should upgrade
|
||||
// their component configs to PreferredVersion or any other supported component config version.
|
||||
ManualUpgradeRequired bool
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// UpgradePlan represents information about upgrade plan for the output
|
||||
@ -56,4 +76,6 @@ type UpgradePlan struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
Components []ComponentUpgradePlan
|
||||
|
||||
ConfigVersions []ComponentConfigVersionState
|
||||
}
|
||||
|
@ -48,6 +48,26 @@ type ComponentUpgradePlan struct {
|
||||
NewVersion string `json:"newVersion"`
|
||||
}
|
||||
|
||||
// ComponentConfigVersionState describes the current and desired version of a component config
|
||||
type ComponentConfigVersionState struct {
|
||||
// Group points to the Kubernetes API group that covers the config
|
||||
Group string `json:"group"`
|
||||
|
||||
// CurrentVersion is the currently active component config version
|
||||
// NOTE: This can be empty in case the config was not found on the cluster or it was unsupported
|
||||
// kubeadm generated version
|
||||
CurrentVersion string `json:"currentVersion"`
|
||||
|
||||
// PreferredVersion is the component config version that is currently preferred by kubeadm for use.
|
||||
// NOTE: As of today, this is the only version supported by kubeadm.
|
||||
PreferredVersion string `json:"preferredVersion"`
|
||||
|
||||
// ManualUpgradeRequired indicates if users need to manually upgrade their component config versions. This happens if
|
||||
// the CurrentVersion of the config is user supplied (or modified) and no longer supported. Users should upgrade
|
||||
// their component configs to PreferredVersion or any other supported component config version.
|
||||
ManualUpgradeRequired bool `json:"manualUpgradeRequired"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// UpgradePlan represents information about upgrade plan for the output
|
||||
@ -56,4 +76,6 @@ type UpgradePlan struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
Components []ComponentUpgradePlan `json:"components"`
|
||||
|
||||
ConfigVersions []ComponentConfigVersionState `json:"configVersions"`
|
||||
}
|
||||
|
@ -45,6 +45,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ComponentConfigVersionState)(nil), (*output.ComponentConfigVersionState)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(a.(*ComponentConfigVersionState), b.(*output.ComponentConfigVersionState), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*output.ComponentConfigVersionState)(nil), (*ComponentConfigVersionState)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(a.(*output.ComponentConfigVersionState), b.(*ComponentConfigVersionState), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ComponentUpgradePlan)(nil), (*output.ComponentUpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_ComponentUpgradePlan_To_output_ComponentUpgradePlan(a.(*ComponentUpgradePlan), b.(*output.ComponentUpgradePlan), scope)
|
||||
}); err != nil {
|
||||
@ -98,6 +108,32 @@ func Convert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in *output.Bootstr
|
||||
return autoConvert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in *ComponentConfigVersionState, out *output.ComponentConfigVersionState, s conversion.Scope) error {
|
||||
out.Group = in.Group
|
||||
out.CurrentVersion = in.CurrentVersion
|
||||
out.PreferredVersion = in.PreferredVersion
|
||||
out.ManualUpgradeRequired = in.ManualUpgradeRequired
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in *ComponentConfigVersionState, out *output.ComponentConfigVersionState, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(in *output.ComponentConfigVersionState, out *ComponentConfigVersionState, s conversion.Scope) error {
|
||||
out.Group = in.Group
|
||||
out.CurrentVersion = in.CurrentVersion
|
||||
out.PreferredVersion = in.PreferredVersion
|
||||
out.ManualUpgradeRequired = in.ManualUpgradeRequired
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState is an autogenerated conversion function.
|
||||
func Convert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(in *output.ComponentConfigVersionState, out *ComponentConfigVersionState, s conversion.Scope) error {
|
||||
return autoConvert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_ComponentUpgradePlan_To_output_ComponentUpgradePlan(in *ComponentUpgradePlan, out *output.ComponentUpgradePlan, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.CurrentVersion = in.CurrentVersion
|
||||
@ -144,6 +180,7 @@ func Convert_output_Images_To_v1alpha1_Images(in *output.Images, out *Images, s
|
||||
|
||||
func autoConvert_v1alpha1_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *output.UpgradePlan, s conversion.Scope) error {
|
||||
out.Components = *(*[]output.ComponentUpgradePlan)(unsafe.Pointer(&in.Components))
|
||||
out.ConfigVersions = *(*[]output.ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -154,6 +191,7 @@ func Convert_v1alpha1_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *ou
|
||||
|
||||
func autoConvert_output_UpgradePlan_To_v1alpha1_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error {
|
||||
out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components))
|
||||
out.ConfigVersions = *(*[]ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,22 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ComponentConfigVersionState) DeepCopyInto(out *ComponentConfigVersionState) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigVersionState.
|
||||
func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ComponentConfigVersionState)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
|
||||
*out = *in
|
||||
@ -105,6 +121,11 @@ func (in *UpgradePlan) DeepCopyInto(out *UpgradePlan) {
|
||||
*out = make([]ComponentUpgradePlan, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ConfigVersions != nil {
|
||||
in, out := &in.ConfigVersions, &out.ConfigVersions
|
||||
*out = make([]ComponentConfigVersionState, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
21
cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go
generated
21
cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go
generated
@ -50,6 +50,22 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ComponentConfigVersionState) DeepCopyInto(out *ComponentConfigVersionState) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigVersionState.
|
||||
func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ComponentConfigVersionState)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
|
||||
*out = *in
|
||||
@ -105,6 +121,11 @@ func (in *UpgradePlan) DeepCopyInto(out *UpgradePlan) {
|
||||
*out = make([]ComponentUpgradePlan, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ConfigVersions != nil {
|
||||
in, out := &in.ConfigVersions, &out.ConfigVersions
|
||||
*out = make([]ComponentConfigVersionState, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ go_library(
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/output/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/output:go_default_library",
|
||||
"//cmd/kubeadm/app/cmd/options:go_default_library",
|
||||
"//cmd/kubeadm/app/cmd/phases/upgrade/node:go_default_library",
|
||||
"//cmd/kubeadm/app/cmd/phases/workflow:go_default_library",
|
||||
@ -38,6 +38,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/discovery/fake:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/github.com/lithammer/dedent:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
|
@ -19,19 +19,25 @@ package upgrade
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/lithammer/dedent"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
outputapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha1"
|
||||
outputapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
)
|
||||
|
||||
type planFlags struct {
|
||||
@ -78,6 +84,13 @@ func runPlan(flags *planFlags, args []string) error {
|
||||
return errors.Wrap(err, "[upgrade/versions] FATAL")
|
||||
}
|
||||
|
||||
// Fetch the current state of the component configs
|
||||
klog.V(1).Infoln("[upgrade/plan] analysing component config version states")
|
||||
configVersionStates, err := getComponentConfigVersionStates(&cfg.ClusterConfiguration, client, flags.cfgPath)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "[upgrade/versions] FATAL")
|
||||
}
|
||||
|
||||
// No upgrades available
|
||||
if len(availUpgrades) == 0 {
|
||||
klog.V(1).Infoln("[upgrade/plan] Awesome, you're up-to-date! Enjoy!")
|
||||
@ -91,14 +104,23 @@ func runPlan(flags *planFlags, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Actually, this is needed for machine readable output only.
|
||||
// printUpgradePlan won't output the configVersionStates as it will simply print the same table several times
|
||||
// in the human readable output if it did so
|
||||
plan.ConfigVersions = configVersionStates
|
||||
|
||||
printUpgradePlan(&up, plan, unstableVersionFlag, isExternalEtcd, os.Stdout)
|
||||
}
|
||||
|
||||
// Finally, print the component config state table
|
||||
printComponentConfigVersionStates(configVersionStates, os.Stdout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newComponentUpgradePlan helper creates outputapiv1alpha1.ComponentUpgradePlan object
|
||||
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv1alpha1.ComponentUpgradePlan {
|
||||
return outputapiv1alpha1.ComponentUpgradePlan{
|
||||
// newComponentUpgradePlan helper creates outputapi.ComponentUpgradePlan object
|
||||
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapi.ComponentUpgradePlan {
|
||||
return outputapi.ComponentUpgradePlan{
|
||||
Name: name,
|
||||
CurrentVersion: currentVersion,
|
||||
NewVersion: newVersion,
|
||||
@ -107,7 +129,7 @@ func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv
|
||||
|
||||
// TODO There is currently no way to cleanly output upgrades that involve adding, removing, or changing components
|
||||
// https://github.com/kubernetes/kubeadm/issues/810 was created to track addressing this.
|
||||
func appendDNSComponent(components []outputapiv1alpha1.ComponentUpgradePlan, up *upgrade.Upgrade, DNSType kubeadmapi.DNSAddOnType, name string) []outputapiv1alpha1.ComponentUpgradePlan {
|
||||
func appendDNSComponent(components []outputapi.ComponentUpgradePlan, up *upgrade.Upgrade, DNSType kubeadmapi.DNSAddOnType, name string) []outputapi.ComponentUpgradePlan {
|
||||
beforeVersion, afterVersion := "", ""
|
||||
if up.Before.DNSType == DNSType {
|
||||
beforeVersion = up.Before.DNSVersion
|
||||
@ -123,7 +145,7 @@ func appendDNSComponent(components []outputapiv1alpha1.ComponentUpgradePlan, up
|
||||
}
|
||||
|
||||
// genUpgradePlan generates output-friendly upgrade plan out of upgrade.Upgrade structure
|
||||
func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha1.UpgradePlan, string, error) {
|
||||
func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapi.UpgradePlan, string, error) {
|
||||
newK8sVersion, err := version.ParseSemantic(up.After.KubeVersion)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "Unable to parse normalized version %q as a semantic version", up.After.KubeVersion)
|
||||
@ -138,7 +160,7 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha
|
||||
}
|
||||
}
|
||||
|
||||
components := []outputapiv1alpha1.ComponentUpgradePlan{}
|
||||
components := []outputapi.ComponentUpgradePlan{}
|
||||
|
||||
if up.CanUpgradeKubelets() {
|
||||
// The map is of the form <old-version>:<node-count>. Here all the keys are put into a slice and sorted
|
||||
@ -161,11 +183,29 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha
|
||||
components = append(components, newComponentUpgradePlan(constants.Etcd, up.Before.EtcdVersion, up.After.EtcdVersion))
|
||||
}
|
||||
|
||||
return &outputapiv1alpha1.UpgradePlan{Components: components}, unstableVersionFlag, nil
|
||||
return &outputapi.UpgradePlan{Components: components}, unstableVersionFlag, nil
|
||||
}
|
||||
|
||||
func getComponentConfigVersionStates(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, cfgPath string) ([]outputapi.ComponentConfigVersionState, error) {
|
||||
docmap := kubeadmapi.DocumentMap{}
|
||||
|
||||
if cfgPath != "" {
|
||||
bytes, err := ioutil.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to read config file %q", cfgPath)
|
||||
}
|
||||
|
||||
docmap, err = kubeadmutil.SplitYAMLDocuments(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return componentconfigs.GetVersionStates(cfg, client, docmap)
|
||||
}
|
||||
|
||||
// printUpgradePlan prints a UX-friendly overview of what versions are available to upgrade to
|
||||
func printUpgradePlan(up *upgrade.Upgrade, plan *outputapiv1alpha1.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, w io.Writer) {
|
||||
func printUpgradePlan(up *upgrade.Upgrade, plan *outputapi.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, w io.Writer) {
|
||||
// The tab writer writes to the "real" writer w
|
||||
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
|
||||
|
||||
@ -217,8 +257,7 @@ func printUpgradePlan(up *upgrade.Upgrade, plan *outputapiv1alpha1.UpgradePlan,
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "_____________________________________________________________________")
|
||||
fmt.Fprintln(w, "")
|
||||
printLineSeparator(w)
|
||||
}
|
||||
|
||||
// sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically
|
||||
@ -230,3 +269,52 @@ func sortedSliceFromStringIntMap(strMap map[string]uint16) []string {
|
||||
sort.Strings(strSlice)
|
||||
return strSlice
|
||||
}
|
||||
|
||||
func strOrDash(s string) string {
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func yesOrNo(b bool) string {
|
||||
if b {
|
||||
return "yes"
|
||||
}
|
||||
return "no"
|
||||
}
|
||||
|
||||
func printLineSeparator(w io.Writer) {
|
||||
fmt.Fprintln(w, "_____________________________________________________________________")
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
|
||||
func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfigVersionState, w io.Writer) {
|
||||
if len(versionStates) == 0 {
|
||||
fmt.Fprintln(w, "No information available on component configs.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, dedent.Dedent(`
|
||||
The table below shows the current state of component configs as understood by this version of kubeadm.
|
||||
Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or
|
||||
resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
|
||||
upgrade to is denoted in the "PREFERRED VERSION" column.
|
||||
`))
|
||||
|
||||
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
|
||||
fmt.Fprintln(tabw, "API GROUP\tCURRENT VERSION\tPREFERRED VERSION\tMANUAL UPGRADE REQUIRED")
|
||||
|
||||
for _, state := range versionStates {
|
||||
fmt.Fprintf(tabw,
|
||||
"%s\t%s\t%s\t%s\n",
|
||||
state.Group,
|
||||
strOrDash(state.CurrentVersion),
|
||||
strOrDash(state.PreferredVersion),
|
||||
yesOrNo(state.ManualUpgradeRequired),
|
||||
)
|
||||
}
|
||||
|
||||
tabw.Flush()
|
||||
printLineSeparator(w)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ go_library(
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/output:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/features:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
@ -51,6 +52,7 @@ go_test(
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/output:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/features:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
)
|
||||
@ -282,6 +283,60 @@ func FetchFromClusterWithLocalOverwrites(clusterCfg *kubeadmapi.ClusterConfigura
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVersionStates returns a slice of ComponentConfigVersionState structs
|
||||
// describing all supported component config groups that were identified on the cluster
|
||||
func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, docmap kubeadmapi.DocumentMap) ([]output.ComponentConfigVersionState, error) {
|
||||
// We don't want to modify clusterCfg so we make a working deep copy of it.
|
||||
// Also, we don't want the defaulted component configs so we get rid of them.
|
||||
scratchClusterCfg := clusterCfg.DeepCopy()
|
||||
scratchClusterCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{}
|
||||
|
||||
// Call FetchFromClusterWithLocalOverwrites. This will populate the configs it can load and will return all
|
||||
// UnsupportedConfigVersionError(s) in a sinle instance of a MultipleUnsupportedConfigVersionsError.
|
||||
var multipleVerErrs UnsupportedConfigVersionsErrorMap
|
||||
err := FetchFromClusterWithLocalOverwrites(scratchClusterCfg, client, docmap)
|
||||
if err != nil {
|
||||
if vererrs, ok := err.(UnsupportedConfigVersionsErrorMap); ok {
|
||||
multipleVerErrs = vererrs
|
||||
} else {
|
||||
// This seems to be a genuine error so we end here
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
results := []output.ComponentConfigVersionState{}
|
||||
for _, handler := range known {
|
||||
group := handler.GroupVersion.Group
|
||||
if vererr, ok := multipleVerErrs[group]; ok {
|
||||
// If there is an UnsupportedConfigVersionError then we are dealing with a case where the config was user
|
||||
// supplied and requires manual upgrade
|
||||
results = append(results, output.ComponentConfigVersionState{
|
||||
Group: group,
|
||||
CurrentVersion: vererr.OldVersion.Version,
|
||||
PreferredVersion: vererr.CurrentVersion.Version,
|
||||
ManualUpgradeRequired: true,
|
||||
})
|
||||
} else if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok {
|
||||
// Normally loaded component config. No manual upgrade required on behalf of users.
|
||||
results = append(results, output.ComponentConfigVersionState{
|
||||
Group: group,
|
||||
CurrentVersion: handler.GroupVersion.Version, // Currently kubeadm supports only one version per API
|
||||
PreferredVersion: handler.GroupVersion.Version, // group so we can get away with these being the same
|
||||
})
|
||||
} else {
|
||||
// This config was either not present (user did not install an addon) or the config was unsupported kubeadm
|
||||
// generated one and is therefore skipped so we can automatically re-generate it (no action required on
|
||||
// behalf of the user).
|
||||
results = append(results, output.ComponentConfigVersionState{
|
||||
Group: group,
|
||||
PreferredVersion: handler.GroupVersion.Version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration
|
||||
// Currently it prints a warning that no validation was performed
|
||||
func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList {
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package componentconfigs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/lithammer/dedent"
|
||||
@ -28,6 +29,7 @@ import (
|
||||
clientsetfake "k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
outputapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
)
|
||||
@ -246,3 +248,406 @@ func TestFetchFromClusterWithLocalUpgrades(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionStates(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
objects []runtime.Object
|
||||
substitutes string
|
||||
expected []outputapi.ComponentConfigVersionState
|
||||
}{
|
||||
{
|
||||
desc: "Normal config",
|
||||
objects: []runtime.Object{
|
||||
kubeproxyConfigMap(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1beta1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Normal config ignoring a current substitute",
|
||||
objects: []runtime.Object{
|
||||
kubeproxyConfigMap(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
substitutes: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1beta1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Normal config with an old substitute",
|
||||
objects: []runtime.Object{
|
||||
kubeproxyConfigMap(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
substitutes: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha0",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1beta1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Old user supplied config",
|
||||
objects: []runtime.Object{
|
||||
kubeproxyConfigMap(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1alpha1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha0",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Old user supplied config with a proper substitute",
|
||||
objects: []runtime.Object{
|
||||
kubeproxyConfigMap(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1alpha1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
substitutes: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Old user supplied config with an old substitute",
|
||||
objects: []runtime.Object{
|
||||
kubeproxyConfigMap(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1alpha1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
substitutes: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha0",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Old kubeadm generated config",
|
||||
objects: []runtime.Object{
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.KubeProxyConfigMap,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Annotations: map[string]string{
|
||||
constants.ComponentConfigHashAnnotationKey: "sha256:8d3dfd7abcac205f6744d8e9db44505cce0c15b0a5395501e272fc18bd54c13c",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeProxyConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1beta1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Old kubeadm generated config with a proper substitute",
|
||||
objects: []runtime.Object{
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.KubeProxyConfigMap,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Annotations: map[string]string{
|
||||
constants.ComponentConfigHashAnnotationKey: "sha256:8d3dfd7abcac205f6744d8e9db44505cce0c15b0a5395501e272fc18bd54c13c",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeProxyConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
substitutes: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha1",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1beta1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Old kubeadm generated config with an old substitute",
|
||||
objects: []runtime.Object{
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.KubeProxyConfigMap,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Annotations: map[string]string{
|
||||
constants.ComponentConfigHashAnnotationKey: "sha256:8d3dfd7abcac205f6744d8e9db44505cce0c15b0a5395501e272fc18bd54c13c",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeProxyConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
substitutes: dedent.Dedent(`
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||
kind: KubeProxyConfiguration
|
||||
`),
|
||||
expected: []outputapi.ComponentConfigVersionState{
|
||||
{
|
||||
Group: "kubeproxy.config.k8s.io",
|
||||
CurrentVersion: "v1alpha0",
|
||||
PreferredVersion: "v1alpha1",
|
||||
ManualUpgradeRequired: true,
|
||||
},
|
||||
{
|
||||
Group: "kubelet.config.k8s.io",
|
||||
CurrentVersion: "v1beta1",
|
||||
PreferredVersion: "v1beta1",
|
||||
ManualUpgradeRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
docmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.substitutes))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
|
||||
}
|
||||
|
||||
clusterCfg := &kubeadmapi.ClusterConfiguration{
|
||||
KubernetesVersion: constants.CurrentKubernetesVersion.String(),
|
||||
}
|
||||
client := clientsetfake.NewSimpleClientset(test.objects...)
|
||||
got, err := GetVersionStates(clusterCfg, client, docmap)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
t.Fatalf("unexpected result:\n\texpected: %v\n\tgot: %v", test.expected, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user