Merge pull request #50872 from luxas/kubeadm_upgrade_cmds

Automatic merge from submit-queue (batch tested with PRs 50872, 51103, 51220, 51285, 50841)

kubeadm: Add 'kubeadm upgrade plan' and 'kubeadm upgrade apply' CLI commands

**What this PR does / why we need it**:

This PR is splitted out from: https://github.com/kubernetes/kubernetes/pull/48899 and only handles the CLI/command code. It adds no-op functions only to `phases/upgrade`.

A large chunk of this code is unit tests.
The code here should be pretty straightforward as there is no actual upgrade or business logic here.
It would be cool to get this merged soon-ish.

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

fixes: https://github.com/kubernetes/kubeadm/issues/14

**Special notes for your reviewer**:

**Release note**:

```release-note
NONE
```
@kubernetes/sig-cluster-lifecycle-pr-reviews PTAL
This commit is contained in:
Kubernetes Submit Queue 2017-08-25 10:10:03 -07:00 committed by GitHub
commit 43c1b5ff9d
34 changed files with 1707 additions and 68 deletions

View File

@ -46,6 +46,7 @@ filegroup(
"//cmd/kubeadm/app/phases/markmaster:all-srcs",
"//cmd/kubeadm/app/phases/selfhosting:all-srcs",
"//cmd/kubeadm/app/phases/token:all-srcs",
"//cmd/kubeadm/app/phases/upgrade:all-srcs",
"//cmd/kubeadm/app/phases/uploadconfig:all-srcs",
"//cmd/kubeadm/app/preflight:all-srcs",
"//cmd/kubeadm/app/util:all-srcs",

View File

@ -23,6 +23,8 @@ go_library(
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/phases:go_default_library",
"//cmd/kubeadm/app/cmd/upgrade:go_default_library",
"//cmd/kubeadm/app/cmd/util:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/discovery:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
@ -95,6 +97,8 @@ filegroup(
srcs = [
":package-srcs",
"//cmd/kubeadm/app/cmd/phases:all-srcs",
"//cmd/kubeadm/app/cmd/upgrade:all-srcs",
"//cmd/kubeadm/app/cmd/util:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"fmt"
"io"
"github.com/renstrom/dedent"
@ -25,6 +24,7 @@ import (
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/upgrade"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
@ -75,6 +75,7 @@ func NewKubeadmCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
cmds.AddCommand(NewCmdReset(out))
cmds.AddCommand(NewCmdVersion(out))
cmds.AddCommand(NewCmdToken(out, err))
cmds.AddCommand(upgrade.NewCmdUpgrade(out))
// Wrap not yet fully supported commands in an alpha subcommand
experimentalCmd := &cobra.Command{
@ -86,18 +87,3 @@ func NewKubeadmCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
return cmds
}
// subCmdRunE returns a function that handles a case where a subcommand must be specified
// Without this callback, if a user runs just the command without a subcommand,
// or with an invalid subcommand, cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
func subCmdRunE(name string) func(*cobra.Command, []string) error {
return func(_ *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("missing subcommand; %q is not meant to be run on its own", name)
}
return fmt.Errorf("invalid subcommand: %q", args[0])
}
}

View File

@ -26,6 +26,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
@ -52,7 +53,7 @@ func NewCmdConfig(out io.Writer) *cobra.Command {
// cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
RunE: subCmdRunE("config"),
RunE: cmdutil.SubCmdRunE("config"),
}
cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster")
@ -67,7 +68,7 @@ func NewCmdConfigUpload(out io.Writer, kubeConfigFile *string) *cobra.Command {
cmd := &cobra.Command{
Use: "upload",
Short: "Upload configuration about the current state so 'kubeadm upgrade' later can know how to configure the upgraded cluster",
RunE: subCmdRunE("upload"),
RunE: cmdutil.SubCmdRunE("upload"),
}
cmd.AddCommand(NewCmdConfigUploadFromFile(out, kubeConfigFile))

View File

@ -25,6 +25,7 @@ go_library(
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/util:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/phases/bootstraptoken/clusterinfo:go_default_library",
@ -54,7 +55,6 @@ go_test(
"controlplane_test.go",
"etcd_test.go",
"kubeconfig_test.go",
"phase_test.go",
],
library = ":go_default_library",
deps = [

View File

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
clientset "k8s.io/client-go/kubernetes"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
@ -36,7 +37,7 @@ func NewCmdBootstrapToken() *cobra.Command {
Use: "bootstrap-token",
Short: "Manage kubeadm-specific Bootstrap Token functions.",
Aliases: []string{"bootstraptoken"},
RunE: subCmdRunE("bootstrap-token"),
RunE: cmdutil.SubCmdRunE("bootstrap-token"),
}
cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster")
@ -55,7 +56,7 @@ func NewSubCmdClusterInfo(kubeConfigFile *string) *cobra.Command {
Short: "Uploads and exposes the cluster-info ConfigMap publicly from the given cluster-info file",
Aliases: []string{"clusterinfo"},
Run: func(cmd *cobra.Command, args []string) {
err := validateExactArgNumber(args, []string{"clusterinfo-file"})
err := cmdutil.ValidateExactArgNumber(args, []string{"clusterinfo-file"})
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
@ -81,7 +82,7 @@ func NewSubCmdNodeBootstrapToken(kubeConfigFile *string) *cobra.Command {
Use: "node",
Short: "Manages Node Bootstrap Tokens",
Aliases: []string{"clusterinfo"},
RunE: subCmdRunE("node"),
RunE: cmdutil.SubCmdRunE("node"),
}
cmd.AddCommand(NewSubCmdNodeBootstrapTokenPostCSRs(kubeConfigFile))

View File

@ -22,6 +22,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
@ -34,7 +35,7 @@ func NewCmdCerts() *cobra.Command {
Use: "certs",
Aliases: []string{"certificates"},
Short: "Generate certificates for a Kubernetes cluster.",
RunE: subCmdRunE("certs"),
RunE: cmdutil.SubCmdRunE("certs"),
}
cmd.AddCommand(getCertsSubCommands()...)

View File

@ -21,6 +21,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
"k8s.io/kubernetes/pkg/api"
@ -31,7 +32,7 @@ func NewCmdControlplane() *cobra.Command {
cmd := &cobra.Command{
Use: "controlplane",
Short: "Generate all static pod manifest files necessary to establish the control plane.",
RunE: subCmdRunE("controlplane"),
RunE: cmdutil.SubCmdRunE("controlplane"),
}
manifestPath := kubeadmconstants.GetStaticPodDirectory()

View File

@ -21,6 +21,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
"k8s.io/kubernetes/pkg/api"
@ -31,7 +32,7 @@ func NewCmdEtcd() *cobra.Command {
cmd := &cobra.Command{
Use: "etcd",
Short: "Generate static pod manifest file for etcd.",
RunE: subCmdRunE("etcd"),
RunE: cmdutil.SubCmdRunE("etcd"),
}
manifestPath := kubeadmconstants.GetStaticPodDirectory()

View File

@ -24,6 +24,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
"k8s.io/kubernetes/pkg/api"
@ -34,7 +35,7 @@ func NewCmdKubeConfig(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "kubeconfig",
Short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.",
RunE: subCmdRunE("kubeconfig"),
RunE: cmdutil.SubCmdRunE("kubeconfig"),
}
cmd.AddCommand(getKubeConfigSubCommands(out, kubeadmconstants.KubernetesDir)...)

View File

@ -19,6 +19,7 @@ package phases
import (
"github.com/spf13/cobra"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
@ -32,7 +33,7 @@ func NewCmdMarkMaster() *cobra.Command {
Short: "Mark a node as master.",
Aliases: []string{"markmaster"},
RunE: func(_ *cobra.Command, args []string) error {
err := validateExactArgNumber(args, []string{"node-name"})
err := cmdutil.ValidateExactArgNumber(args, []string{"node-name"})
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)

View File

@ -17,10 +17,10 @@ limitations under the License.
package phases
import (
"fmt"
"io"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
)
// NewCmdPhase returns the cobra command for the "kubeadm phase" command (currently alpha-gated)
@ -28,7 +28,7 @@ func NewCmdPhase(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "phase",
Short: "Invoke subsets of kubeadm functions separately for a manual install.",
RunE: subCmdRunE("phase"),
RunE: cmdutil.SubCmdRunE("phase"),
}
cmd.AddCommand(NewCmdBootstrapToken())
@ -43,37 +43,3 @@ func NewCmdPhase(out io.Writer) *cobra.Command {
return cmd
}
// subCmdRunE returns a function that handles a case where a subcommand must be specified
// Without this callback, if a user runs just the command without a subcommand,
// or with an invalid subcommand, cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
func subCmdRunE(name string) func(*cobra.Command, []string) error {
return func(_ *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("missing subcommand; %q is not meant to be run on its own", name)
}
return fmt.Errorf("invalid subcommand: %q", args[0])
}
}
// validateExactArgNumber validates that the required top-level arguments are specified
func validateExactArgNumber(args []string, supportedArgs []string) error {
validArgs := 0
// Disregard possible "" arguments; they are invalid
for _, arg := range args {
if len(arg) > 0 {
validArgs++
}
}
if validArgs < len(supportedArgs) {
return fmt.Errorf("missing one or more required arguments. Required arguments: %v", supportedArgs)
}
if validArgs > len(supportedArgs) {
return fmt.Errorf("too many arguments, only %d argument(s) supported: %v", validArgs, supportedArgs)
}
return nil
}

View File

@ -20,6 +20,7 @@ import (
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
)
@ -27,7 +28,7 @@ func NewCmdPreFlight() *cobra.Command {
cmd := &cobra.Command{
Use: "preflight",
Short: "Run pre-flight checks",
RunE: subCmdRunE("preflight"),
RunE: cmdutil.SubCmdRunE("preflight"),
}
cmd.AddCommand(NewCmdPreFlightMaster())

View File

@ -23,6 +23,7 @@ import (
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
@ -37,7 +38,7 @@ func NewCmdSelfhosting() *cobra.Command {
Use: "selfhosting",
Aliases: []string{"selfhosted"},
Short: "Make a kubeadm cluster self-hosted.",
RunE: subCmdRunE("selfhosting"),
RunE: cmdutil.SubCmdRunE("selfhosting"),
}
cmd.AddCommand(getSelfhostingSubCommand())

View File

@ -33,6 +33,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
@ -77,7 +78,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
// cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
RunE: subCmdRunE("token"),
RunE: cmdutil.SubCmdRunE("token"),
}
tokenCmd.PersistentFlags().StringVar(&kubeConfigFile,

View File

@ -0,0 +1,55 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"apply.go",
"common.go",
"plan.go",
"upgrade.go",
],
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/cmd/util:go_default_library",
"//cmd/kubeadm/app/phases/upgrade:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//pkg/api:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"apply_test.go",
"common_test.go",
"plan_test.go",
],
library = ":go_default_library",
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/phases/upgrade:go_default_library",
"//pkg/util/version:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,213 @@
/*
Copyright 2017 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 upgrade
import (
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/version"
)
// applyFlags holds the information about the flags that can be passed to apply
type applyFlags struct {
nonInteractiveMode bool
force bool
dryRun bool
newK8sVersionStr string
newK8sVersion *version.Version
imagePullTimeout time.Duration
parent *cmdUpgradeFlags
}
// SessionIsInteractive returns true if the session is of an interactive type (the default, can be opted out of with -y, -f or --dry-run)
func (f *applyFlags) SessionIsInteractive() bool {
return !f.nonInteractiveMode
}
// NewCmdApply returns the cobra command for `kubeadm upgrade apply`
func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
flags := &applyFlags{
parent: parentFlags,
imagePullTimeout: 15 * time.Minute,
}
cmd := &cobra.Command{
Use: "apply [version]",
Short: "Upgrade your Kubernetes cluster to the specified version",
Run: func(cmd *cobra.Command, args []string) {
// Ensure the user is root
err := runPreflightChecks(flags.parent.skipPreFlight)
kubeadmutil.CheckErr(err)
err = cmdutil.ValidateExactArgNumber(args, []string{"version"})
kubeadmutil.CheckErr(err)
// It's safe to use args[0] here as the slice has been validated above
flags.newK8sVersionStr = args[0]
// Default the flags dynamically, based on each others' value
err = SetImplicitFlags(flags)
kubeadmutil.CheckErr(err)
err = RunApply(flags)
kubeadmutil.CheckErr(err)
},
}
// Specify the valid flags specific for apply
cmd.Flags().BoolVarP(&flags.nonInteractiveMode, "yes", "y", flags.nonInteractiveMode, "Perform the upgrade and do not prompt for confirmation (non-interactive mode).")
cmd.Flags().BoolVarP(&flags.force, "force", "f", flags.force, "Force upgrading although some requirements might not be met. This also implies non-interactive mode.")
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output what actions would be applied.")
cmd.Flags().DurationVar(&flags.imagePullTimeout, "image-pull-timeout", flags.imagePullTimeout, "The maximum amount of time to wait for the control plane pods to be downloaded.")
return cmd
}
// RunApply takes care of the actual upgrade functionality
// It does the following things:
// - Checks if the cluster is healthy
// - Gets the configuration from the kubeadm-config ConfigMap in the cluster
// - Enforces all version skew policies
// - Asks the user if they really want to upgrade
// - Makes sure the control plane images are available locally on the master(s)
// - Upgrades the control plane components
// - Applies the other resources that'd be created with kubeadm init as well, like
// - Creating the RBAC rules for the Bootstrap Tokens and the cluster-info ConfigMap
// - Applying new kube-dns and kube-proxy manifests
// - Uploads the newly used configuration to the cluster ConfigMap
func RunApply(flags *applyFlags) error {
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
upgradeVars, err := enforceRequirements(flags.parent.kubeConfigPath, flags.parent.cfgPath, flags.parent.printConfig)
if err != nil {
return err
}
// Set the upgraded version on the external config object now
upgradeVars.cfg.KubernetesVersion = flags.newK8sVersionStr
// Grab the external, versioned configuration and convert it to the internal type for usage here later
internalcfg := &kubeadmapi.MasterConfiguration{}
api.Scheme.Convert(upgradeVars.cfg, internalcfg, nil)
// Enforce the version skew policies
if err := EnforceVersionPolicies(flags, upgradeVars.versionGetter); err != nil {
return fmt.Errorf("[upgrade/version] FATAL: %v", err)
}
// If the current session is interactive, ask the user whether they really want to upgrade
if flags.SessionIsInteractive() {
if err := InteractivelyConfirmUpgrade("Are you sure you want to proceed with the upgrade?"); err != nil {
return err
}
}
// TODO: Implement a prepulling mechanism here
// Now; perform the upgrade procedure
if err := PerformControlPlaneUpgrade(flags, upgradeVars.client, internalcfg); err != nil {
return fmt.Errorf("[upgrade/apply] FATAL: %v", err)
}
// Upgrade RBAC rules and addons. Optionally, if needed, perform some extra task for a specific version
if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, internalcfg, flags.newK8sVersion); err != nil {
return fmt.Errorf("[upgrade/postupgrade] FATAL: %v", err)
}
fmt.Println("")
fmt.Printf("[upgrade/successful] SUCCESS! Your cluster was upgraded to %q. Enjoy!\n", flags.newK8sVersionStr)
fmt.Println("")
fmt.Println("[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets in turn.")
return nil
}
// SetImplicitFlags handles dynamically defaulting flags based on each other's value
func SetImplicitFlags(flags *applyFlags) error {
// If we are in dry-run or force mode; we should automatically execute this command non-interactively
if flags.dryRun || flags.force {
flags.nonInteractiveMode = true
}
k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
if err != nil {
return fmt.Errorf("couldn't parse version %q as a semantic version", flags.newK8sVersionStr)
}
flags.newK8sVersion = k8sVer
// Automatically add the "v" prefix to the string representation in case it doesn't exist
if !strings.HasPrefix(flags.newK8sVersionStr, "v") {
flags.newK8sVersionStr = fmt.Sprintf("v%s", flags.newK8sVersionStr)
}
return nil
}
// EnforceVersionPolicies makes sure that the version the user specified is valid to upgrade to
// There are both fatal and skippable (with --force) errors
func EnforceVersionPolicies(flags *applyFlags, versionGetter upgrade.VersionGetter) error {
fmt.Printf("[upgrade/version] You have chosen to upgrade to version %q\n", flags.newK8sVersionStr)
versionSkewErrs := upgrade.EnforceVersionPolicies(versionGetter, flags.newK8sVersionStr, flags.newK8sVersion, flags.parent.allowExperimentalUpgrades, flags.parent.allowRCUpgrades)
if versionSkewErrs != nil {
if len(versionSkewErrs.Mandatory) > 0 {
return fmt.Errorf("The --version argument is invalid due to these fatal errors: %v", versionSkewErrs.Mandatory)
}
if len(versionSkewErrs.Skippable) > 0 {
// Return the error if the user hasn't specified the --force flag
if !flags.force {
return fmt.Errorf("The --version argument is invalid due to these errors: %v. Can be bypassed if you pass the --force flag", versionSkewErrs.Mandatory)
}
// Soft errors found, but --force was specified
fmt.Printf("[upgrade/version] Found %d potential version compatibility errors but skipping since the --force flag is set: %v\n", len(versionSkewErrs.Skippable), versionSkewErrs.Skippable)
}
}
return nil
}
// PerformControlPlaneUpgrade actually performs the upgrade procedure for the cluster of your type (self-hosted or static-pod-hosted)
func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, internalcfg *kubeadmapi.MasterConfiguration) error {
// Check if the cluster is self-hosted and act accordingly
if upgrade.IsControlPlaneSelfHosted(client) {
fmt.Printf("[upgrade/apply] Upgrading your Self-Hosted control plane to version %q...\n", flags.newK8sVersionStr)
// Upgrade a self-hosted cluster
// TODO(luxas): Implement this later when we have the new upgrade strategy
return fmt.Errorf("not implemented")
}
// OK, the cluster is hosted using static pods. Upgrade a static-pod hosted cluster
fmt.Printf("[upgrade/apply] Upgrading your Static Pod-hosted control plane to version %q...\n", flags.newK8sVersionStr)
if err := upgrade.PerformStaticPodControlPlaneUpgrade(client, internalcfg, flags.newK8sVersion); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,194 @@
/*
Copyright 2017 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 upgrade
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/util/version"
)
func TestSetImplicitFlags(t *testing.T) {
var tests = []struct {
flags *applyFlags
expectedFlags applyFlags
errExpected bool
}{
{ // if not dryRun or force is set; the nonInteractiveMode field should not be touched
flags: &applyFlags{
newK8sVersionStr: "v1.8.0",
dryRun: false,
force: false,
nonInteractiveMode: false,
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
dryRun: false,
force: false,
nonInteractiveMode: false,
},
},
{ // if not dryRun or force is set; the nonInteractiveMode field should not be touched
flags: &applyFlags{
newK8sVersionStr: "v1.8.0",
dryRun: false,
force: false,
nonInteractiveMode: true,
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
dryRun: false,
force: false,
nonInteractiveMode: true,
},
},
{ // if dryRun or force is set; the nonInteractiveMode field should be set to true
flags: &applyFlags{
newK8sVersionStr: "v1.8.0",
dryRun: true,
force: false,
nonInteractiveMode: false,
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
dryRun: true,
force: false,
nonInteractiveMode: true,
},
},
{ // if dryRun or force is set; the nonInteractiveMode field should be set to true
flags: &applyFlags{
newK8sVersionStr: "v1.8.0",
dryRun: false,
force: true,
nonInteractiveMode: false,
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
dryRun: false,
force: true,
nonInteractiveMode: true,
},
},
{ // if dryRun or force is set; the nonInteractiveMode field should be set to true
flags: &applyFlags{
newK8sVersionStr: "v1.8.0",
dryRun: true,
force: true,
nonInteractiveMode: false,
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
dryRun: true,
force: true,
nonInteractiveMode: true,
},
},
{ // if dryRun or force is set; the nonInteractiveMode field should be set to true
flags: &applyFlags{
newK8sVersionStr: "v1.8.0",
dryRun: true,
force: true,
nonInteractiveMode: true,
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
dryRun: true,
force: true,
nonInteractiveMode: true,
},
},
{ // if the new version is empty; it should error out
flags: &applyFlags{
newK8sVersionStr: "",
},
expectedFlags: applyFlags{
newK8sVersionStr: "",
},
errExpected: true,
},
{ // if the new version is invalid; it should error out
flags: &applyFlags{
newK8sVersionStr: "foo",
},
expectedFlags: applyFlags{
newK8sVersionStr: "foo",
},
errExpected: true,
},
{ // if the new version is valid but without the "v" prefix; it parse and prepend v
flags: &applyFlags{
newK8sVersionStr: "1.8.0",
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0",
newK8sVersion: version.MustParseSemantic("v1.8.0"),
},
errExpected: false,
},
{ // valid version should succeed
flags: &applyFlags{
newK8sVersionStr: "v1.8.1",
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.1",
newK8sVersion: version.MustParseSemantic("v1.8.1"),
},
errExpected: false,
},
{ // valid version should succeed
flags: &applyFlags{
newK8sVersionStr: "1.8.0-alpha.3",
},
expectedFlags: applyFlags{
newK8sVersionStr: "v1.8.0-alpha.3",
newK8sVersion: version.MustParseSemantic("v1.8.0-alpha.3"),
},
errExpected: false,
},
}
for _, rt := range tests {
actualErr := SetImplicitFlags(rt.flags)
// If an error was returned; make newK8sVersion nil so it's easy to match using reflect.DeepEqual later (instead of a random pointer)
if actualErr != nil {
rt.flags.newK8sVersion = nil
}
if !reflect.DeepEqual(*rt.flags, rt.expectedFlags) {
t.Errorf(
"failed SetImplicitFlags:\n\texpected flags: %v\n\t actual: %v",
rt.expectedFlags,
*rt.flags,
)
}
if (actualErr != nil) != rt.errExpected {
t.Errorf(
"failed SetImplicitFlags:\n\texpected error: %t\n\t actual: %t",
rt.errExpected,
(actualErr != nil),
)
}
}
}

View File

@ -0,0 +1,120 @@
/*
Copyright 2017 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 upgrade
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
"github.com/ghodss/yaml"
clientset "k8s.io/client-go/kubernetes"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
)
// upgradeVariables holds variables needed for performing an upgrade or planning to do so
// TODO - Restructure or rename upgradeVariables
type upgradeVariables struct {
client clientset.Interface
cfg *kubeadmapiext.MasterConfiguration
versionGetter upgrade.VersionGetter
}
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
func enforceRequirements(kubeConfigPath, cfgPath string, printConfig bool) (*upgradeVariables, error) {
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigPath)
if err != nil {
return nil, fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", kubeConfigPath, err)
}
// Run healthchecks against the cluster
if err := upgrade.CheckClusterHealth(client); err != nil {
return nil, fmt.Errorf("[upgrade/health] FATAL: %v", err)
}
// Fetch the configuration from a file or ConfigMap and validate it
cfg, err := upgrade.FetchConfiguration(client, os.Stdout, cfgPath)
if err != nil {
return nil, fmt.Errorf("[upgrade/config] FATAL: %v", err)
}
// If the user told us to print this information out; do it!
if printConfig {
printConfiguration(cfg, os.Stdout)
}
return &upgradeVariables{
client: client,
cfg: cfg,
// Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions
versionGetter: upgrade.NewKubeVersionGetter(client, os.Stdout),
}, nil
}
// printConfiguration prints the external version of the API to yaml
func printConfiguration(cfg *kubeadmapiext.MasterConfiguration, w io.Writer) {
// Short-circuit if cfg is nil, so we can safely get the value of the pointer below
if cfg == nil {
return
}
cfgYaml, err := yaml.Marshal(*cfg)
if err == nil {
fmt.Fprintln(w, "[upgrade/config] Configuration used:")
scanner := bufio.NewScanner(bytes.NewReader(cfgYaml))
for scanner.Scan() {
fmt.Fprintf(w, "\t%s\n", scanner.Text())
}
}
}
// runPreflightChecks runs the root preflight check
func runPreflightChecks(skipPreFlight bool) error {
if skipPreFlight {
fmt.Println("[preflight] Skipping pre-flight checks")
return nil
}
fmt.Println("[preflight] Running pre-flight checks")
return preflight.RunRootCheckOnly()
}
// InteractivelyConfirmUpgrade asks the user whether they _really_ want to upgrade.
func InteractivelyConfirmUpgrade(question string) error {
fmt.Printf("[upgrade/confirm] %s [y/N]: ", question)
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if err := scanner.Err(); err != nil {
return fmt.Errorf("couldn't read from standard input: %v", err)
}
answer := scanner.Text()
if strings.ToLower(answer) == "y" || strings.ToLower(answer) == "yes" {
return nil
}
return fmt.Errorf("won't proceed; the user didn't answer (Y|y) in order to continue")
}

View File

@ -0,0 +1,124 @@
/*
Copyright 2017 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 upgrade
import (
"bytes"
"testing"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
func TestPrintConfiguration(t *testing.T) {
var tests = []struct {
cfg *kubeadmapiext.MasterConfiguration
buf *bytes.Buffer
expectedBytes []byte
}{
{
cfg: nil,
expectedBytes: []byte(""),
},
{
cfg: &kubeadmapiext.MasterConfiguration{
KubernetesVersion: "v1.7.1",
},
expectedBytes: []byte(`[upgrade/config] Configuration used:
api:
advertiseAddress: ""
bindPort: 0
apiServerCertSANs: null
apiServerExtraArgs: null
authorizationModes: null
certificatesDir: ""
cloudProvider: ""
controllerManagerExtraArgs: null
etcd:
caFile: ""
certFile: ""
dataDir: ""
endpoints: null
extraArgs: null
image: ""
keyFile: ""
featureFlags: null
imageRepository: ""
kubernetesVersion: v1.7.1
networking:
dnsDomain: ""
podSubnet: ""
serviceSubnet: ""
nodeName: ""
schedulerExtraArgs: null
token: ""
tokenTTL: 0
unifiedControlPlaneImage: ""
`),
},
{
cfg: &kubeadmapiext.MasterConfiguration{
KubernetesVersion: "v1.7.1",
Networking: kubeadmapiext.Networking{
ServiceSubnet: "10.96.0.1/12",
},
},
expectedBytes: []byte(`[upgrade/config] Configuration used:
api:
advertiseAddress: ""
bindPort: 0
apiServerCertSANs: null
apiServerExtraArgs: null
authorizationModes: null
certificatesDir: ""
cloudProvider: ""
controllerManagerExtraArgs: null
etcd:
caFile: ""
certFile: ""
dataDir: ""
endpoints: null
extraArgs: null
image: ""
keyFile: ""
featureFlags: null
imageRepository: ""
kubernetesVersion: v1.7.1
networking:
dnsDomain: ""
podSubnet: ""
serviceSubnet: 10.96.0.1/12
nodeName: ""
schedulerExtraArgs: null
token: ""
tokenTTL: 0
unifiedControlPlaneImage: ""
`),
},
}
for _, rt := range tests {
rt.buf = bytes.NewBufferString("")
printConfiguration(rt.cfg, rt.buf)
actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) {
t.Errorf(
"failed PrintConfiguration:\n\texpected: %q\n\t actual: %q",
string(rt.expectedBytes),
string(actualBytes),
)
}
}
}

View File

@ -0,0 +1,142 @@
/*
Copyright 2017 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 upgrade
import (
"fmt"
"io"
"os"
"sort"
"text/tabwriter"
"github.com/spf13/cobra"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
// NewCmdPlan returns the cobra command for `kubeadm upgrade plan`
func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
cmd := &cobra.Command{
Use: "plan",
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable",
Run: func(_ *cobra.Command, _ []string) {
// Ensure the user is root
err := runPreflightChecks(parentFlags.skipPreFlight)
kubeadmutil.CheckErr(err)
err = RunPlan(parentFlags)
kubeadmutil.CheckErr(err)
},
}
return cmd
}
// RunPlan takes care of outputting available versions to upgrade to for the user
func RunPlan(parentFlags *cmdUpgradeFlags) error {
// Start with the basics, verify that the cluster is healthy, build a client and a versionGetter.
upgradeVars, err := enforceRequirements(parentFlags.kubeConfigPath, parentFlags.cfgPath, parentFlags.printConfig)
if err != nil {
return err
}
// Compute which upgrade possibilities there are
availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, parentFlags.allowExperimentalUpgrades, parentFlags.allowRCUpgrades)
if err != nil {
return err
}
// Tell the user which upgrades are available
printAvailableUpgrades(availUpgrades, os.Stdout)
return nil
}
// printAvailableUpgrades prints a UX-friendly overview of what versions are available to upgrade to
// TODO look into columnize or some other formatter when time permits instead of using the tabwriter
func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer) {
// Return quickly if no upgrades can be made
if len(upgrades) == 0 {
fmt.Fprintln(w, "Awesome, you're up-to-date! Enjoy!")
return
}
// The tab writer writes to the "real" writer w
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
// Loop through the upgrade possibilities and output text to the command line
for _, upgrade := range upgrades {
if upgrade.CanUpgradeKubelets() {
fmt.Fprintln(w, "Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tAVAILABLE")
firstPrinted := false
// The map is of the form <old-version>:<node-count>. Here all the keys are put into a slice and sorted
// in order to always get the right order. Then the map value is extracted separately
for _, oldVersion := range sortedSliceFromStringIntMap(upgrade.Before.KubeletVersions) {
nodeCount := upgrade.Before.KubeletVersions[oldVersion]
if !firstPrinted {
// Output the Kubelet header only on the first version pair
fmt.Fprintf(tabw, "Kubelet\t%d x %s\t%s\n", nodeCount, oldVersion, upgrade.After.KubeVersion)
firstPrinted = true
continue
}
fmt.Fprintf(tabw, "\t\t%d x %s\t%s\n", nodeCount, oldVersion, upgrade.After.KubeVersion)
}
// We should flush the writer here at this stage; as the columns will now be of the right size, adjusted to the above content
tabw.Flush()
fmt.Fprintln(w, "")
}
fmt.Fprintf(w, "Upgrade to the latest %s:\n", upgrade.Description)
fmt.Fprintln(w, "")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tAVAILABLE")
fmt.Fprintf(tabw, "API Server\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "Controller Manager\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "Scheduler\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "Kube Proxy\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "Kube DNS\t%s\t%s\n", upgrade.Before.DNSVersion, upgrade.After.DNSVersion)
// The tabwriter should be flushed at this stage as we have now put in all the required content for this time. This is required for the tabs' size to be correct.
tabw.Flush()
fmt.Fprintln(w, "")
fmt.Fprintln(w, "You can now apply the upgrade by executing the following command:")
fmt.Fprintln(w, "")
fmt.Fprintf(w, "\tkubeadm upgrade apply %s\n", upgrade.After.KubeVersion)
fmt.Fprintln(w, "")
if upgrade.Before.KubeadmVersion != upgrade.After.KubeadmVersion {
fmt.Fprintf(w, "Note: Before you do can perform this upgrade, you have to update kubeadm to %s\n", upgrade.After.KubeadmVersion)
fmt.Fprintln(w, "")
}
fmt.Fprintln(w, "_____________________________________________________________________")
fmt.Fprintln(w, "")
}
}
// sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically
func sortedSliceFromStringIntMap(strMap map[string]uint16) []string {
strSlice := []string{}
for k := range strMap {
strSlice = append(strSlice, k)
}
sort.Strings(strSlice)
return strSlice
}

View File

@ -0,0 +1,329 @@
/*
Copyright 2017 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 upgrade
import (
"bytes"
"reflect"
"testing"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
)
func TestSortedSliceFromStringIntMap(t *testing.T) {
var tests = []struct {
strMap map[string]uint16
expectedSlice []string
}{ // The returned slice should be alphabetically sorted based on the string keys in the map
{
strMap: map[string]uint16{"foo": 1, "bar": 2},
expectedSlice: []string{"bar", "foo"},
},
{ // The int value should not affect this func
strMap: map[string]uint16{"foo": 2, "bar": 1},
expectedSlice: []string{"bar", "foo"},
},
{
strMap: map[string]uint16{"b": 2, "a": 1, "cb": 0, "ca": 1000},
expectedSlice: []string{"a", "b", "ca", "cb"},
},
{ // This should work for version numbers as well; and the lowest version should come first
strMap: map[string]uint16{"v1.7.0": 1, "v1.6.1": 1, "v1.6.2": 1, "v1.8.0": 1, "v1.8.0-alpha.1": 1},
expectedSlice: []string{"v1.6.1", "v1.6.2", "v1.7.0", "v1.8.0", "v1.8.0-alpha.1"},
},
}
for _, rt := range tests {
actualSlice := sortedSliceFromStringIntMap(rt.strMap)
if !reflect.DeepEqual(actualSlice, rt.expectedSlice) {
t.Errorf(
"failed SortedSliceFromStringIntMap:\n\texpected: %v\n\t actual: %v",
rt.expectedSlice,
actualSlice,
)
}
}
}
// TODO Think about modifying this test to be less verbose checking b/c it can be brittle.
func TestPrintAvailableUpgrades(t *testing.T) {
var tests = []struct {
upgrades []upgrade.Upgrade
buf *bytes.Buffer
expectedBytes []byte
}{
{
upgrades: []upgrade.Upgrade{},
expectedBytes: []byte(`Awesome, you're up-to-date! Enjoy!
`),
},
{
upgrades: []upgrade.Upgrade{
{
Description: "version in the v1.7 series",
Before: upgrade.ClusterState{
KubeVersion: "v1.7.1",
KubeletVersions: map[string]uint16{
"v1.7.1": 1,
},
KubeadmVersion: "v1.7.2",
DNSVersion: "1.14.4",
},
After: upgrade.ClusterState{
KubeVersion: "v1.7.3",
KubeadmVersion: "v1.7.3",
DNSVersion: "1.14.4",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.7.1 v1.7.3
Upgrade to the latest version in the v1.7 series:
COMPONENT CURRENT AVAILABLE
API Server v1.7.1 v1.7.3
Controller Manager v1.7.1 v1.7.3
Scheduler v1.7.1 v1.7.3
Kube Proxy v1.7.1 v1.7.3
Kube DNS 1.14.4 1.14.4
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.7.3
Note: Before you do can perform this upgrade, you have to update kubeadm to v1.7.3
_____________________________________________________________________
`),
},
{
upgrades: []upgrade.Upgrade{
{
Description: "stable version",
Before: upgrade.ClusterState{
KubeVersion: "v1.7.3",
KubeletVersions: map[string]uint16{
"v1.7.3": 1,
},
KubeadmVersion: "v1.8.0",
DNSVersion: "1.14.4",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.0",
KubeadmVersion: "v1.8.0",
DNSVersion: "1.14.4",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.7.3 v1.8.0
Upgrade to the latest stable version:
COMPONENT CURRENT AVAILABLE
API Server v1.7.3 v1.8.0
Controller Manager v1.7.3 v1.8.0
Scheduler v1.7.3 v1.8.0
Kube Proxy v1.7.3 v1.8.0
Kube DNS 1.14.4 1.14.4
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.8.0
_____________________________________________________________________
`),
},
{
upgrades: []upgrade.Upgrade{
{
Description: "version in the v1.7 series",
Before: upgrade.ClusterState{
KubeVersion: "v1.7.3",
KubeletVersions: map[string]uint16{
"v1.7.3": 1,
},
KubeadmVersion: "v1.8.1",
DNSVersion: "1.14.4",
},
After: upgrade.ClusterState{
KubeVersion: "v1.7.5",
KubeadmVersion: "v1.8.1",
DNSVersion: "1.14.4",
},
},
{
Description: "stable version",
Before: upgrade.ClusterState{
KubeVersion: "v1.7.3",
KubeletVersions: map[string]uint16{
"v1.7.3": 1,
},
KubeadmVersion: "v1.8.1",
DNSVersion: "1.14.4",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.2",
KubeadmVersion: "v1.8.2",
DNSVersion: "1.14.4",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.7.3 v1.7.5
Upgrade to the latest version in the v1.7 series:
COMPONENT CURRENT AVAILABLE
API Server v1.7.3 v1.7.5
Controller Manager v1.7.3 v1.7.5
Scheduler v1.7.3 v1.7.5
Kube Proxy v1.7.3 v1.7.5
Kube DNS 1.14.4 1.14.4
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.7.5
_____________________________________________________________________
Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.7.3 v1.8.2
Upgrade to the latest stable version:
COMPONENT CURRENT AVAILABLE
API Server v1.7.3 v1.8.2
Controller Manager v1.7.3 v1.8.2
Scheduler v1.7.3 v1.8.2
Kube Proxy v1.7.3 v1.8.2
Kube DNS 1.14.4 1.14.4
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.8.2
Note: Before you do can perform this upgrade, you have to update kubeadm to v1.8.2
_____________________________________________________________________
`),
},
{
upgrades: []upgrade.Upgrade{
{
Description: "experimental version",
Before: upgrade.ClusterState{
KubeVersion: "v1.7.5",
KubeletVersions: map[string]uint16{
"v1.7.5": 1,
},
KubeadmVersion: "v1.7.5",
DNSVersion: "1.14.4",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.0-beta.1",
KubeadmVersion: "v1.8.0-beta.1",
DNSVersion: "1.14.4",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.7.5 v1.8.0-beta.1
Upgrade to the latest experimental version:
COMPONENT CURRENT AVAILABLE
API Server v1.7.5 v1.8.0-beta.1
Controller Manager v1.7.5 v1.8.0-beta.1
Scheduler v1.7.5 v1.8.0-beta.1
Kube Proxy v1.7.5 v1.8.0-beta.1
Kube DNS 1.14.4 1.14.4
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.8.0-beta.1
Note: Before you do can perform this upgrade, you have to update kubeadm to v1.8.0-beta.1
_____________________________________________________________________
`),
},
{
upgrades: []upgrade.Upgrade{
{
Description: "release candidate version",
Before: upgrade.ClusterState{
KubeVersion: "v1.7.5",
KubeletVersions: map[string]uint16{
"v1.7.5": 1,
},
KubeadmVersion: "v1.7.5",
DNSVersion: "1.14.4",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.0-rc.1",
KubeadmVersion: "v1.8.0-rc.1",
DNSVersion: "1.14.4",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.7.5 v1.8.0-rc.1
Upgrade to the latest release candidate version:
COMPONENT CURRENT AVAILABLE
API Server v1.7.5 v1.8.0-rc.1
Controller Manager v1.7.5 v1.8.0-rc.1
Scheduler v1.7.5 v1.8.0-rc.1
Kube Proxy v1.7.5 v1.8.0-rc.1
Kube DNS 1.14.4 1.14.4
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.8.0-rc.1
Note: Before you do can perform this upgrade, you have to update kubeadm to v1.8.0-rc.1
_____________________________________________________________________
`),
},
}
for _, rt := range tests {
rt.buf = bytes.NewBufferString("")
printAvailableUpgrades(rt.upgrades, rt.buf)
actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) {
t.Errorf(
"failed PrintAvailableUpgrades:\n\texpected: %q\n\t actual: %q",
string(rt.expectedBytes),
string(actualBytes),
)
}
}
}

View File

@ -0,0 +1,64 @@
/*
Copyright 2017 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 upgrade
import (
"io"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
)
// cmdUpgradeFlags holds the values for the common flags in `kubeadm upgrade`
type cmdUpgradeFlags struct {
kubeConfigPath string
cfgPath string
allowExperimentalUpgrades bool
allowRCUpgrades bool
printConfig bool
skipPreFlight bool
}
// NewCmdUpgrade returns the cobra command for `kubeadm upgrade`
func NewCmdUpgrade(out io.Writer) *cobra.Command {
flags := &cmdUpgradeFlags{
kubeConfigPath: "/etc/kubernetes/admin.conf",
cfgPath: "",
allowExperimentalUpgrades: false,
allowRCUpgrades: false,
printConfig: false,
skipPreFlight: false,
}
cmd := &cobra.Command{
Use: "upgrade",
Short: "Upgrade your cluster smoothly to a newer version with this command.",
RunE: cmdutil.SubCmdRunE("upgrade"),
}
cmd.PersistentFlags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use for talking to the cluster.")
cmd.PersistentFlags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental).")
cmd.PersistentFlags().BoolVar(&flags.allowExperimentalUpgrades, "allow-experimental-upgrades", flags.allowExperimentalUpgrades, "Show unstable versions of Kubernetes as an upgrade alternative and allow upgrading to an alpha/beta/release candidate versions of Kubernetes.")
cmd.PersistentFlags().BoolVar(&flags.allowRCUpgrades, "allow-release-candidate-upgrades", flags.allowRCUpgrades, "Show release candidate versions of Kubernetes as an upgrade alternative and allow upgrading to a release candidate versions of Kubernetes.")
cmd.PersistentFlags().BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Whether the configuration file that will be used in the upgrade should be printed or not.")
cmd.PersistentFlags().BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks normally run before modifying the system")
cmd.AddCommand(NewCmdApply(flags))
cmd.AddCommand(NewCmdPlan(flags))
return cmd
}

View File

@ -0,0 +1,28 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["cmdutil.go"],
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/spf13/cobra:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["cmdutil_test.go"],
library = ":go_default_library",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,57 @@
/*
Copyright 2017 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 phases
import (
"fmt"
"github.com/spf13/cobra"
)
// SubCmdRunE returns a function that handles a case where a subcommand must be specified
// Without this callback, if a user runs just the command without a subcommand,
// or with an invalid subcommand, cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
func SubCmdRunE(name string) func(*cobra.Command, []string) error {
return func(_ *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("missing subcommand; %q is not meant to be run on its own", name)
}
return fmt.Errorf("invalid subcommand: %q", args[0])
}
}
// ValidateExactArgNumber validates that the required top-level arguments are specified
func ValidateExactArgNumber(args []string, supportedArgs []string) error {
validArgs := 0
// Disregard possible "" arguments; they are invalid
for _, arg := range args {
if len(arg) > 0 {
validArgs++
}
}
if validArgs < len(supportedArgs) {
return fmt.Errorf("missing one or more required arguments. Required arguments: %v", supportedArgs)
}
if validArgs > len(supportedArgs) {
return fmt.Errorf("too many arguments, only %d argument(s) supported: %v", validArgs, supportedArgs)
}
return nil
}

View File

@ -52,10 +52,10 @@ func TestValidateExactArgNumber(t *testing.T) {
},
}
for _, rt := range tests {
actual := validateExactArgNumber(rt.args, rt.supportedArgs)
actual := ValidateExactArgNumber(rt.args, rt.supportedArgs)
if (actual != nil) != rt.expectedErr {
t.Errorf(
"failed validateExactArgNumber:\n\texpected error: %t\n\t actual error: %t",
"failed ValidateExactArgNumber:\n\texpected error: %t\n\t actual error: %t",
rt.expectedErr,
(actual != nil),
)

View File

@ -0,0 +1,36 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"compute.go",
"configuration.go",
"health.go",
"policy.go",
"postupgrade.go",
"staticpods.go",
"versiongetter.go",
],
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//pkg/api:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,63 @@
/*
Copyright 2017 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 upgrade
import (
"fmt"
)
// Upgrade defines an upgrade possibility to upgrade from a current version to a new one
type Upgrade struct {
Description string
Before ClusterState
After ClusterState
}
// CanUpgradeKubelets returns whether an upgrade of any kubelet in the cluster is possible
func (u *Upgrade) CanUpgradeKubelets() bool {
// If there are multiple different versions now, an upgrade is possible (even if only for a subset of the nodes)
if len(u.Before.KubeletVersions) > 1 {
return true
}
// Don't report something available for upgrade if we don't know the current state
if len(u.Before.KubeletVersions) == 0 {
return false
}
// if the same version number existed both before and after, we don't have to upgrade it
_, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion]
return !sameVersionFound
}
// ClusterState describes the state of certain versions for a cluster
type ClusterState struct {
// KubeVersion describes the version of the Kubernetes API Server, Controller Manager, Scheduler and Proxy.
KubeVersion string
// DNSVersion describes the version of the kube-dns images used and manifest version
DNSVersion string
// KubeadmVersion describes the version of the kubeadm CLI
KubeadmVersion string
// KubeletVersions is a map with a version number linked to the amount of kubelets running that version in the cluster
KubeletVersions map[string]uint16
}
// GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
// kinds of upgrades can be performed
func GetAvailableUpgrades(_ VersionGetter, _, _ bool) ([]Upgrade, error) {
fmt.Println("[upgrade] Fetching available versions to upgrade to:")
return []Upgrade{}, nil
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2017 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 upgrade
import (
"fmt"
"io"
clientset "k8s.io/client-go/kubernetes"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/pkg/api"
)
// FetchConfiguration fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster
func FetchConfiguration(_ clientset.Interface, _ io.Writer, _ string) (*kubeadmapiext.MasterConfiguration, error) {
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
cfg := &kubeadmapiext.MasterConfiguration{}
api.Scheme.Default(cfg)
return cfg, nil
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2017 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 upgrade
import (
clientset "k8s.io/client-go/kubernetes"
)
// CheckClusterHealth makes sure:
// - the API /healthz endpoint is healthy
// - all Nodes are Ready
// - (if self-hosted) that there are DaemonSets with at least one Pod for all control plane components
// - (if static pod-hosted) that all required Static Pod manifests exist on disk
func CheckClusterHealth(_ clientset.Interface) error {
return nil
}
// IsControlPlaneSelfHosted returns whether the control plane is self hosted or not
func IsControlPlaneSelfHosted(_ clientset.Interface) bool {
// No-op for now
return false
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2017 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 upgrade
import (
"k8s.io/kubernetes/pkg/util/version"
)
// VersionSkewPolicyErrors describes version skew errors that might be seen during the validation process in EnforceVersionPolicies
type VersionSkewPolicyErrors struct {
Mandatory []error
Skippable []error
}
// EnforceVersionPolicies enforces that the proposed new version is compatible with all the different version skew policies
func EnforceVersionPolicies(_ VersionGetter, _ string, _ *version.Version, _, _ bool) *VersionSkewPolicyErrors {
// No-op now and return no skew errors
return nil
}

View File

@ -0,0 +1,30 @@
/*
Copyright 2017 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 upgrade
import (
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/pkg/util/version"
)
// PerformPostUpgradeTasks runs nearly the same functions as 'kubeadm init' would do
// Note that the markmaster phase is left out, not needed, and no token is created as that doesn't belong to the upgrade
func PerformPostUpgradeTasks(_ clientset.Interface, _ *kubeadmapi.MasterConfiguration, _ *version.Version) error {
// No-op; don't do anything here yet
return nil
}

View File

@ -0,0 +1,29 @@
/*
Copyright 2017 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 upgrade
import (
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/pkg/util/version"
)
// PerformStaticPodControlPlaneUpgrade upgrades a static pod-hosted control plane
func PerformStaticPodControlPlaneUpgrade(_ clientset.Interface, _ *kubeadmapi.MasterConfiguration, _ *version.Version) error {
// No-op for now; doesn't do anything yet
return nil
}

View File

@ -0,0 +1,83 @@
/*
Copyright 2017 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 upgrade
import (
"fmt"
"io"
clientset "k8s.io/client-go/kubernetes"
versionutil "k8s.io/kubernetes/pkg/util/version"
)
// VersionGetter defines an interface for fetching different versions.
// Easy to implement a fake variant of this interface for unit testing
type VersionGetter interface {
// ClusterVersion should return the version of the cluster i.e. the API Server version
ClusterVersion() (string, *versionutil.Version, error)
// KubeadmVersion should return the version of the kubeadm CLI
KubeadmVersion() (string, *versionutil.Version, error)
// VersionFromCILabel should resolve CI labels like `latest`, `stable`, `stable-1.8`, etc. to real versions
VersionFromCILabel(string, string) (string, *versionutil.Version, error)
// KubeletVersions should return a map with a version and a number that describes how many kubelets there are for that version
KubeletVersions() (map[string]uint16, error)
}
// KubeVersionGetter handles the version-fetching mechanism from external sources
type KubeVersionGetter struct {
client clientset.Interface
w io.Writer
}
// Make sure KubeVersionGetter implements the VersionGetter interface
var _ VersionGetter = &KubeVersionGetter{}
// NewKubeVersionGetter returns a new instance of KubeVersionGetter
func NewKubeVersionGetter(client clientset.Interface, writer io.Writer) *KubeVersionGetter {
return &KubeVersionGetter{
client: client,
w: writer,
}
}
// ClusterVersion gets API server version
func (g *KubeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) {
fmt.Fprintf(g.w, "[upgrade/versions] Cluster version: ")
fmt.Fprintln(g.w, "v1.7.0")
return "v1.7.0", versionutil.MustParseSemantic("v1.7.0"), nil
}
// KubeadmVersion gets kubeadm version
func (g *KubeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) {
fmt.Fprintf(g.w, "[upgrade/versions] kubeadm version: %s\n", "v1.8.0")
return "v1.8.0", versionutil.MustParseSemantic("v1.8.0"), nil
}
// VersionFromCILabel resolves different labels like "stable" to action semver versions using the Kubernetes CI uploads to GCS
func (g *KubeVersionGetter) VersionFromCILabel(_, _ string) (string, *versionutil.Version, error) {
return "v1.8.1", versionutil.MustParseSemantic("v1.8.0"), nil
}
// KubeletVersions gets the versions of the kubelets in the cluster
func (g *KubeVersionGetter) KubeletVersions() (map[string]uint16, error) {
// This tells kubeadm that there are two nodes in the cluster; both on the v1.7.1 version currently
return map[string]uint16{
"v1.7.1": 2,
}, nil
}