From 3201cc4dd89b3cf155021964c6c9e06f300015e1 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Sun, 14 Jun 2020 14:43:36 -0700 Subject: [PATCH] Require extra flag when updating cluster with downgraded kops version --- cmd/kops/update_cluster.go | 13 +++--- docs/cli/kops_update_cluster.md | 1 + docs/releases/1.19-NOTES.md | 3 ++ pkg/apis/kops/registry/registry.go | 2 + pkg/client/simple/vfsclientset/clientset.go | 2 +- upup/pkg/fi/cloudup/BUILD.bazel | 1 + upup/pkg/fi/cloudup/apply_cluster.go | 48 ++++++++++++++++++--- 7 files changed, 59 insertions(+), 11 deletions(-) diff --git a/cmd/kops/update_cluster.go b/cmd/kops/update_cluster.go index 9a1ca220de054..b31fa3905d2cd 100644 --- a/cmd/kops/update_cluster.go +++ b/cmd/kops/update_cluster.go @@ -59,11 +59,12 @@ var ( ) type UpdateClusterOptions struct { - Yes bool - Target string - OutDir string - SSHPublicKey string - RunTasksOptions fi.RunTasksOptions + Yes bool + Target string + OutDir string + SSHPublicKey string + RunTasksOptions fi.RunTasksOptions + AllowKopsDowngrade bool CreateKubecfg bool admin bool @@ -117,6 +118,7 @@ func NewCmdUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command { cmd.Flags().BoolVar(&options.CreateKubecfg, "create-kube-config", options.CreateKubecfg, "Will control automatically creating the kube config file on your local filesystem") cmd.Flags().BoolVar(&options.admin, "admin", options.admin, "Also export the admin user. Implies --create-kube-config") cmd.Flags().StringVar(&options.user, "user", options.user, "Existing user to add to the cluster context. Implies --create-kube-config") + cmd.Flags().BoolVar(&options.AllowKopsDowngrade, "allow-kops-downgrade", options.AllowKopsDowngrade, "Allow an older version of kops to update the cluster than last used") cmd.Flags().StringVar(&options.Phase, "phase", options.Phase, "Subset of tasks to run: "+strings.Join(cloudup.Phases.List(), ", ")) cmd.Flags().StringSliceVar(&options.LifecycleOverrides, "lifecycle-overrides", options.LifecycleOverrides, "comma separated list of phase overrides, example: SecurityGroups=Ignore,InternetGateway=ExistsAndWarnIfChanges") viper.BindPFlag("lifecycle-overrides", cmd.Flags().Lookup("lifecycle-overrides")) @@ -259,6 +261,7 @@ func RunUpdateCluster(ctx context.Context, f *util.Factory, clusterName string, Clientset: clientset, Cluster: cluster, DryRun: isDryrun, + AllowKopsDowngrade: c.AllowKopsDowngrade, RunTasksOptions: &c.RunTasksOptions, OutDir: c.OutDir, Phase: phase, diff --git a/docs/cli/kops_update_cluster.md b/docs/cli/kops_update_cluster.md index f99682cba204e..725bcfe6491b1 100644 --- a/docs/cli/kops_update_cluster.md +++ b/docs/cli/kops_update_cluster.md @@ -26,6 +26,7 @@ kops update cluster [flags] ``` --admin Also export the admin user. Implies --create-kube-config + --allow-kops-downgrade Allow an older version of kops to update the cluster than last used --create-kube-config Will control automatically creating the kube config file on your local filesystem -h, --help help for cluster --lifecycle-overrides strings comma separated list of phase overrides, example: SecurityGroups=Ignore,InternetGateway=ExistsAndWarnIfChanges diff --git a/docs/releases/1.19-NOTES.md b/docs/releases/1.19-NOTES.md index e30d9b3f02b66..95c562d3636b0 100644 --- a/docs/releases/1.19-NOTES.md +++ b/docs/releases/1.19-NOTES.md @@ -22,6 +22,9 @@ Similarly, `kops export kubecfg` will also require passing either the `--admin` * New clusters running Cilium will have enabled BPF NodePort by default if kubernetes version is 1.12 or newer. +* The `kops update cluster` command will now refuse to run on a cluster that +has been updated by a newer version of kops unless it is given the `--allow-kops-downgrade` flag. + # Breaking changes * Support for Kubernetes 1.9 and 1.10 has been removed. diff --git a/pkg/apis/kops/registry/registry.go b/pkg/apis/kops/registry/registry.go index 918aa549f4854..a233946e9be99 100644 --- a/pkg/apis/kops/registry/registry.go +++ b/pkg/apis/kops/registry/registry.go @@ -29,6 +29,8 @@ const ( PathCluster = "config" // Path for completed cluster spec in the state store PathClusterCompleted = "cluster.spec" + // PathKopsVersionUpdated is the path for the version of kops last used to apply the cluster. + PathKopsVersionUpdated = "kops-version.txt" ) func ConfigBase(c *api.Cluster) (vfs.Path, error) { diff --git a/pkg/client/simple/vfsclientset/clientset.go b/pkg/client/simple/vfsclientset/clientset.go index 3ec4bcb5ca768..be9c52b9f2fc8 100644 --- a/pkg/client/simple/vfsclientset/clientset.go +++ b/pkg/client/simple/vfsclientset/clientset.go @@ -139,7 +139,7 @@ func DeleteAllClusterState(basePath vfs.Path) error { continue } - if relativePath == "config" || relativePath == "cluster.spec" { + if relativePath == "config" || relativePath == "cluster.spec" || relativePath == registry.PathKopsVersionUpdated { continue } if strings.HasPrefix(relativePath, "addons/") { diff --git a/upup/pkg/fi/cloudup/BUILD.bazel b/upup/pkg/fi/cloudup/BUILD.bazel index e741409c10d68..8d96d5b34ad77 100644 --- a/upup/pkg/fi/cloudup/BUILD.bazel +++ b/upup/pkg/fi/cloudup/BUILD.bazel @@ -31,6 +31,7 @@ go_library( "//dnsprovider/pkg/dnsprovider:go_default_library", "//dnsprovider/pkg/dnsprovider/providers/aws/route53:go_default_library", "//dnsprovider/pkg/dnsprovider/rrstype:go_default_library", + "//pkg/acls:go_default_library", "//pkg/apis/kops:go_default_library", "//pkg/apis/kops/model:go_default_library", "//pkg/apis/kops/registry:go_default_library", diff --git a/upup/pkg/fi/cloudup/apply_cluster.go b/upup/pkg/fi/cloudup/apply_cluster.go index 10d3f794826a5..3892905c183e2 100644 --- a/upup/pkg/fi/cloudup/apply_cluster.go +++ b/upup/pkg/fi/cloudup/apply_cluster.go @@ -17,6 +17,7 @@ limitations under the License. package cloudup import ( + "bytes" "context" "fmt" "net/url" @@ -28,6 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog" kopsbase "k8s.io/kops" + "k8s.io/kops/pkg/acls" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/registry" "k8s.io/kops/pkg/apis/kops/util" @@ -112,6 +114,9 @@ type ApplyClusterCmd struct { // DryRun is true if this is only a dry run DryRun bool + // AllowKopsDowngrade permits applying with a kops version older than what was last used to apply to the cluster. + AllowKopsDowngrade bool + // RunTasksOptions defines parameters for task execution, e.g. retry interval RunTasksOptions *fi.RunTasksOptions @@ -229,6 +234,35 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error { cluster := c.Cluster + configBase, err := vfs.Context.BuildVfsPath(cluster.Spec.ConfigBase) + if err != nil { + return fmt.Errorf("error parsing config base %q: %v", cluster.Spec.ConfigBase, err) + } + + if !c.AllowKopsDowngrade { + kopsVersionUpdatedBytes, err := configBase.Join(registry.PathKopsVersionUpdated).ReadFile() + if err == nil { + kopsVersionUpdated := strings.TrimSpace(string(kopsVersionUpdatedBytes)) + version, err := semver.Parse(kopsVersionUpdated) + if err != nil { + return fmt.Errorf("error parsing last kops version updated: %v", err) + } + if version.GT(semver.MustParse(kopsbase.Version)) { + fmt.Printf("\n") + fmt.Printf("%s\n", starline) + fmt.Printf("\n") + fmt.Printf("The cluster was last updated by kops version %s\n", kopsVersionUpdated) + fmt.Printf("To permit updating by the older version %s, run with the --allow-kops-downgrade flag\n", kopsbase.Version) + fmt.Printf("\n") + fmt.Printf("%s\n", starline) + fmt.Printf("\n") + return fmt.Errorf("kops version older than last used to update the cluster") + } + } else if err != os.ErrNotExist { + return fmt.Errorf("error reading last kops version used to update: %v", err) + } + } + cloud, err := BuildCloud(cluster) if err != nil { return err @@ -250,11 +284,6 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error { l.Init() l.Cluster = c.Cluster - configBase, err := vfs.Context.BuildVfsPath(cluster.Spec.ConfigBase) - if err != nil { - return fmt.Errorf("error parsing config base %q: %v", cluster.Spec.ConfigBase, err) - } - keyStore, err := c.Clientset.KeyStore(cluster) if err != nil { return err @@ -743,6 +772,15 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error { c.Target = target if !dryRun { + acl, err := acls.GetACL(configBase, cluster) + if err != nil { + return err + } + err = configBase.Join(registry.PathKopsVersionUpdated).WriteFile(bytes.NewReader([]byte(kopsbase.Version)), acl) + if err != nil { + return fmt.Errorf("error writing kops version: %v", err) + } + err = registry.WriteConfigDeprecated(cluster, configBase.Join(registry.PathClusterCompleted), c.Cluster) if err != nil { return fmt.Errorf("error writing completed cluster spec: %v", err)