mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 04:06:03 +00:00
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:
commit
43c1b5ff9d
@ -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",
|
||||
|
@ -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"],
|
||||
)
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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 = [
|
||||
|
@ -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))
|
||||
|
@ -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()...)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)...)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
55
cmd/kubeadm/app/cmd/upgrade/BUILD
Normal file
55
cmd/kubeadm/app/cmd/upgrade/BUILD
Normal 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"],
|
||||
)
|
213
cmd/kubeadm/app/cmd/upgrade/apply.go
Normal file
213
cmd/kubeadm/app/cmd/upgrade/apply.go
Normal 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
|
||||
}
|
194
cmd/kubeadm/app/cmd/upgrade/apply_test.go
Normal file
194
cmd/kubeadm/app/cmd/upgrade/apply_test.go
Normal 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
120
cmd/kubeadm/app/cmd/upgrade/common.go
Normal file
120
cmd/kubeadm/app/cmd/upgrade/common.go
Normal 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")
|
||||
}
|
124
cmd/kubeadm/app/cmd/upgrade/common_test.go
Normal file
124
cmd/kubeadm/app/cmd/upgrade/common_test.go
Normal 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
142
cmd/kubeadm/app/cmd/upgrade/plan.go
Normal file
142
cmd/kubeadm/app/cmd/upgrade/plan.go
Normal 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
|
||||
}
|
329
cmd/kubeadm/app/cmd/upgrade/plan_test.go
Normal file
329
cmd/kubeadm/app/cmd/upgrade/plan_test.go
Normal 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
64
cmd/kubeadm/app/cmd/upgrade/upgrade.go
Normal file
64
cmd/kubeadm/app/cmd/upgrade/upgrade.go
Normal 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
|
||||
}
|
28
cmd/kubeadm/app/cmd/util/BUILD
Normal file
28
cmd/kubeadm/app/cmd/util/BUILD
Normal 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"],
|
||||
)
|
57
cmd/kubeadm/app/cmd/util/cmdutil.go
Normal file
57
cmd/kubeadm/app/cmd/util/cmdutil.go
Normal 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
|
||||
}
|
@ -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),
|
||||
)
|
36
cmd/kubeadm/app/phases/upgrade/BUILD
Normal file
36
cmd/kubeadm/app/phases/upgrade/BUILD
Normal 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"],
|
||||
)
|
63
cmd/kubeadm/app/phases/upgrade/compute.go
Normal file
63
cmd/kubeadm/app/phases/upgrade/compute.go
Normal 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
|
||||
}
|
36
cmd/kubeadm/app/phases/upgrade/configuration.go
Normal file
36
cmd/kubeadm/app/phases/upgrade/configuration.go
Normal 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
|
||||
}
|
36
cmd/kubeadm/app/phases/upgrade/health.go
Normal file
36
cmd/kubeadm/app/phases/upgrade/health.go
Normal 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
|
||||
}
|
33
cmd/kubeadm/app/phases/upgrade/policy.go
Normal file
33
cmd/kubeadm/app/phases/upgrade/policy.go
Normal 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
|
||||
}
|
30
cmd/kubeadm/app/phases/upgrade/postupgrade.go
Normal file
30
cmd/kubeadm/app/phases/upgrade/postupgrade.go
Normal 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
|
||||
}
|
29
cmd/kubeadm/app/phases/upgrade/staticpods.go
Normal file
29
cmd/kubeadm/app/phases/upgrade/staticpods.go
Normal 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
|
||||
}
|
83
cmd/kubeadm/app/phases/upgrade/versiongetter.go
Normal file
83
cmd/kubeadm/app/phases/upgrade/versiongetter.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user