diff --git a/cmd/kops/create_cluster.go b/cmd/kops/create_cluster.go index 7db3a00ce6..192982dd35 100644 --- a/cmd/kops/create_cluster.go +++ b/cmd/kops/create_cluster.go @@ -662,12 +662,13 @@ func RunCreateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Cr updateClusterOptions.Target = c.Target updateClusterOptions.OutDir = c.OutDir updateClusterOptions.admin = kubeconfig.DefaultKubecfgAdminLifetime + updateClusterOptions.ClusterName = cluster.Name updateClusterOptions.CreateKubecfg = true // SSHPublicKey has already been mapped updateClusterOptions.SSHPublicKey = "" - _, err := RunUpdateCluster(ctx, f, cluster.Name, out, updateClusterOptions) + _, err := RunUpdateCluster(ctx, f, out, updateClusterOptions) if err != nil { return err } diff --git a/cmd/kops/export_kubecfg.go b/cmd/kops/export_kubecfg.go index eed4c882b0..32e379ff5c 100644 --- a/cmd/kops/export_kubecfg.go +++ b/cmd/kops/export_kubecfg.go @@ -27,6 +27,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/kops/cmd/kops/util" kopsapi "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/pkg/commands/commandutils" "k8s.io/kops/pkg/kubeconfig" "k8s.io/kops/upup/pkg/fi/cloudup" "k8s.io/kubectl/pkg/util/i18n" @@ -176,3 +177,19 @@ func buildPathOptions(options *ExportKubecfgOptions) *clientcmd.PathOptions { return pathOptions } + +func completeKubecfgUser(cmd *cobra.Command, args []string, complete string) ([]string, cobra.ShellCompDirective) { + pathOptions := clientcmd.NewDefaultPathOptions() + + config, err := pathOptions.GetStartingConfig() + if err != nil { + return commandutils.CompletionError("reading kubeconfig", err) + } + + var users []string + for user := range config.AuthInfos { + users = append(users, user) + } + + return users, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/kops/get_assets.go b/cmd/kops/get_assets.go index ac2a0d6c28..31ffed4839 100644 --- a/cmd/kops/get_assets.go +++ b/cmd/kops/get_assets.go @@ -104,9 +104,10 @@ func RunGetAssets(ctx context.Context, f *util.Factory, out io.Writer, options * return fmt.Errorf("--name is required") } - updateClusterResults, err := RunUpdateCluster(ctx, f, clusterName, out, &UpdateClusterOptions{ - Target: cloudup.TargetDryRun, - GetAssets: true, + updateClusterResults, err := RunUpdateCluster(ctx, f, out, &UpdateClusterOptions{ + Target: cloudup.TargetDryRun, + GetAssets: true, + ClusterName: clusterName, }) if err != nil { return err diff --git a/cmd/kops/integration_test.go b/cmd/kops/integration_test.go index fafbb4f87d..51003d077c 100644 --- a/cmd/kops/integration_test.go +++ b/cmd/kops/integration_test.go @@ -599,10 +599,10 @@ func (i *integrationTest) runTest(t *testing.T, h *testutils.IntegrationTestHarn // We don't test it here, and it adds a dependency on kubectl options.CreateKubecfg = false - + options.ClusterName = i.clusterName options.LifecycleOverrides = i.lifecycleOverrides - _, err := RunUpdateCluster(ctx, factory, i.clusterName, &stdout, options) + _, err := RunUpdateCluster(ctx, factory, &stdout, options) if err != nil { t.Fatalf("error running update cluster %q: %v", i.clusterName, err) } @@ -1008,9 +1008,10 @@ func (i *integrationTest) runTestCloudformation(t *testing.T) { // We don't test it here, and it adds a dependency on kubectl options.CreateKubecfg = false + options.ClusterName = i.clusterName options.LifecycleOverrides = i.lifecycleOverrides - _, err := RunUpdateCluster(ctx, factory, i.clusterName, &stdout, options) + _, err := RunUpdateCluster(ctx, factory, &stdout, options) if err != nil { t.Fatalf("error running update cluster %q: %v", i.clusterName, err) } diff --git a/cmd/kops/lifecycle_integration_test.go b/cmd/kops/lifecycle_integration_test.go index 6c73fa776c..25a3280c4e 100644 --- a/cmd/kops/lifecycle_integration_test.go +++ b/cmd/kops/lifecycle_integration_test.go @@ -502,8 +502,9 @@ func updateEnsureNoChanges(ctx context.Context, t *testing.T, factory *util.Fact // We don't test it here, and it adds a dependency on kubectl options.CreateKubecfg = false + options.ClusterName = clusterName - _, err := RunUpdateCluster(ctx, factory, clusterName, &stdout, options) + _, err := RunUpdateCluster(ctx, factory, &stdout, options) if err != nil { t.Fatalf("error running update cluster %q: %v", clusterName, err) } @@ -517,8 +518,9 @@ func updateEnsureNoChanges(ctx context.Context, t *testing.T, factory *util.Fact // We don't test it here, and it adds a dependency on kubectl options.CreateKubecfg = false + options.ClusterName = clusterName - results, err := RunUpdateCluster(ctx, factory, clusterName, &stdout, options) + results, err := RunUpdateCluster(ctx, factory, &stdout, options) if err != nil { t.Fatalf("error running update cluster %q: %v", clusterName, err) } diff --git a/cmd/kops/update.go b/cmd/kops/update.go index a74940d7e2..bfbf096a12 100644 --- a/cmd/kops/update.go +++ b/cmd/kops/update.go @@ -22,28 +22,16 @@ import ( "github.com/spf13/cobra" "k8s.io/kops/cmd/kops/util" "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" ) var ( - updateLong = templates.LongDesc(i18n.T(` - Creates or updates cloud resources to match cluster desired configuration. - `)) - - updateExample = templates.Examples(i18n.T(` - # After cluster has been created, configure it with: - kops update cluster k8s-cluster.example.com --yes --state=s3://my-state-store - `)) - updateShort = i18n.T("Update a cluster.") ) func NewCmdUpdate(f *util.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "update", - Short: updateShort, - Long: updateLong, - Example: updateExample, + Use: "update", + Short: updateShort, } // subcommands diff --git a/cmd/kops/update_cluster.go b/cmd/kops/update_cluster.go index cbea3c4cae..78e9ad6b0f 100644 --- a/cmd/kops/update_cluster.go +++ b/cmd/kops/update_cluster.go @@ -33,6 +33,7 @@ import ( "k8s.io/kops/cmd/kops/util" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/assets" + "k8s.io/kops/pkg/commands/commandutils" "k8s.io/kops/pkg/kubeconfig" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup" @@ -44,16 +45,16 @@ import ( var ( updateClusterLong = templates.LongDesc(i18n.T(` - Create or update cloud or cluster resources to match current cluster state. If the cluster or cloud resources already - exist this command may modify those resources. + Create or update cloud or cluster resources to match the current cluster and instance group definitions. + If the cluster or cloud resources already exist this command may modify those resources. - If nodes need updating such as during a Kubernetes upgrade, a rolling-update may - be required as well. + If, such as during a Kubernetes upgrade, nodes need updating, a rolling-update may + be subsequently required. `)) updateClusterExample = templates.Examples(i18n.T(` - # After cluster has been edited or upgraded, configure it with: - kops update cluster k8s-cluster.example.com --yes --state=s3://my-state-store --yes --admin + # After the cluster has been edited or upgraded, update the cloud resources with: + kops update cluster k8s-cluster.example.com --yes --state=s3://my-state-store --yes `)) updateClusterShort = i18n.T("Update a cluster.") @@ -69,6 +70,8 @@ type UpdateClusterOptions struct { // GetAssets is whether this is invoked from the CmdGetAssets. GetAssets bool + ClusterName string + CreateKubecfg bool admin time.Duration user string @@ -98,40 +101,41 @@ func NewCmdUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command { options.InitDefaults() cmd := &cobra.Command{ - Use: "cluster", - Short: updateClusterShort, - Long: updateClusterLong, - Example: updateClusterExample, - Run: func(cmd *cobra.Command, args []string) { - ctx := context.TODO() - - err := rootCommand.ProcessArgs(args) - if err != nil { - exitWithError(err) - } - - clusterName := rootCommand.ClusterName(true) - - if _, err := RunUpdateCluster(ctx, f, clusterName, out, options); err != nil { - exitWithError(err) - } + Use: "cluster [CLUSTER]", + Short: updateClusterShort, + Long: updateClusterLong, + Example: updateClusterExample, + Args: rootCommand.clusterNameArgs(&options.ClusterName), + ValidArgsFunction: commandutils.CompleteClusterName(&rootCommand, true), + RunE: func(cmd *cobra.Command, args []string) error { + _, err := RunUpdateCluster(context.TODO(), f, out, options) + return err }, } cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Create cloud resources, without --yes update is in dry run mode") cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform, cloudformation") + cmd.RegisterFlagCompletionFunc("target", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{cloudup.TargetDirect, cloudup.TargetDryRun, cloudup.TargetTerraform, cloudup.TargetCloudformation}, cobra.ShellCompDirectiveNoFileComp + }) cmd.Flags().StringVar(&options.SSHPublicKey, "ssh-public-key", options.SSHPublicKey, "SSH public key to use (deprecated: use kops create secret instead)") cmd.Flags().StringVar(&options.OutDir, "out", options.OutDir, "Path to write any local output") + cmd.MarkFlagDirname("out") cmd.Flags().BoolVar(&options.CreateKubecfg, "create-kube-config", options.CreateKubecfg, "Will control automatically creating the kube config file on your local filesystem") cmd.Flags().DurationVar(&options.admin, "admin", options.admin, "Also export a cluster admin user credential with the specified lifetime and add it to the cluster context") cmd.Flags().Lookup("admin").NoOptDefVal = kubeconfig.DefaultKubecfgAdminLifetime.String() cmd.Flags().StringVar(&options.user, "user", options.user, "Re-use an existing user in kubeconfig. Value must specify an existing user block in your kubeconfig file. Implies --create-kube-config") + cmd.RegisterFlagCompletionFunc("user", completeKubecfgUser) cmd.Flags().BoolVar(&options.internal, "internal", options.internal, "Use the cluster's internal DNS name. 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.RegisterFlagCompletionFunc("phase", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return cloudup.Phases.List(), cobra.ShellCompDirectiveNoFileComp + }) 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")) viper.BindEnv("lifecycle-overrides", "KOPS_LIFECYCLE_OVERRIDES") + cmd.RegisterFlagCompletionFunc("lifecycle-overrides", completeLifecycleOverrides) return cmd } @@ -151,7 +155,7 @@ type UpdateClusterResults struct { Cluster *kops.Cluster } -func RunUpdateCluster(ctx context.Context, f *util.Factory, clusterName string, out io.Writer, c *UpdateClusterOptions) (*UpdateClusterResults, error) { +func RunUpdateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *UpdateClusterOptions) (*UpdateClusterResults, error) { results := &UpdateClusterResults{} isDryrun := false @@ -198,7 +202,7 @@ func RunUpdateCluster(ctx context.Context, f *util.Factory, clusterName string, } } - cluster, err := GetCluster(ctx, f, clusterName) + cluster, err := GetCluster(ctx, f, c.ClusterName) if err != nil { return results, err } @@ -368,7 +372,7 @@ func RunUpdateCluster(ctx context.Context, f *util.Factory, clusterName string, fmt.Fprintf(sb, "Cloudformation output has been placed into %s\n", c.OutDir) if firstRun { - cfName := "kubernetes-" + strings.Replace(clusterName, ".", "-", -1) + cfName := "kubernetes-" + strings.Replace(c.ClusterName, ".", "-", -1) cfPath := filepath.Join(c.OutDir, "kubernetes.json") fmt.Fprintf(sb, "Run this command to apply the configuration:\n") fmt.Fprintf(sb, " aws cloudformation create-stack --capabilities CAPABILITY_NAMED_IAM --stack-name %s --template-body file://%s\n", cfName, cfPath) @@ -465,3 +469,21 @@ func hasKubecfg(contextName string) (bool, error) { } return false, nil } + +func completeLifecycleOverrides(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + split := strings.SplitAfter(toComplete, "=") + + if len(split) < 2 { + // providing completion for task names is too complicated + return nil, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace + } + if len(split) > 2 { + return commandutils.CompletionError("too many = characters", nil) + } + + var completions []string + for lifecycle := range fi.LifecycleNameMap { + completions = append(completions, split[0]+lifecycle) + } + return completions, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/kops/version.go b/cmd/kops/version.go index 7675c68561..bed55c2236 100644 --- a/cmd/kops/version.go +++ b/cmd/kops/version.go @@ -45,16 +45,16 @@ func NewCmdVersion(f *util.Factory, out io.Writer) *cobra.Command { Short: versionShort, Long: versionLong, Example: versionExample, + Args: cobra.NoArgs, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveNoFileComp + }, + RunE: func(cmd *cobra.Command, args []string) error { + return commands.RunVersion(f, out, options) + }, } - cmd.Run = func(cmd *cobra.Command, args []string) { - err := commands.RunVersion(f, out, options) - if err != nil { - exitWithError(err) - } - } - - cmd.Flags().BoolVar(&options.Short, "short", options.Short, "only print the main kOps version, useful for scripting") + cmd.Flags().BoolVar(&options.Short, "short", options.Short, "only print the main kOps version. Useful for scripting.") return cmd } diff --git a/docs/cli/kops_update.md b/docs/cli/kops_update.md index cefd1a4b5f..891b218326 100644 --- a/docs/cli/kops_update.md +++ b/docs/cli/kops_update.md @@ -5,17 +5,6 @@ Update a cluster. -### Synopsis - -Creates or updates cloud resources to match cluster desired configuration. - -### Examples - -``` - # After cluster has been created, configure it with: - kops update cluster k8s-cluster.example.com --yes --state=s3://my-state-store -``` - ### Options ``` diff --git a/docs/cli/kops_update_cluster.md b/docs/cli/kops_update_cluster.md index 700975f11c..8446419cfb 100644 --- a/docs/cli/kops_update_cluster.md +++ b/docs/cli/kops_update_cluster.md @@ -7,19 +7,19 @@ Update a cluster. ### Synopsis -Create or update cloud or cluster resources to match current cluster state. If the cluster or cloud resources already exist this command may modify those resources. +Create or update cloud or cluster resources to match the current cluster and instance group definitions. If the cluster or cloud resources already exist this command may modify those resources. - If nodes need updating such as during a Kubernetes upgrade, a rolling-update may be required as well. + If, such as during a Kubernetes upgrade, nodes need updating, a rolling-update may be subsequently required. ``` -kops update cluster [flags] +kops update cluster [CLUSTER] [flags] ``` ### Examples ``` - # After cluster has been edited or upgraded, configure it with: - kops update cluster k8s-cluster.example.com --yes --state=s3://my-state-store --yes --admin + # After the cluster has been edited or upgraded, update the cloud resources with: + kops update cluster k8s-cluster.example.com --yes --state=s3://my-state-store --yes ``` ### Options diff --git a/docs/cli/kops_version.md b/docs/cli/kops_version.md index 0fde2f93d3..1d829b7bc1 100644 --- a/docs/cli/kops_version.md +++ b/docs/cli/kops_version.md @@ -23,7 +23,7 @@ kops version [flags] ``` -h, --help help for version - --short only print the main kOps version, useful for scripting + --short only print the main kOps version. Useful for scripting. ``` ### Options inherited from parent commands diff --git a/mkdocs.yml b/mkdocs.yml index 4a0b5db51a..006e4bade9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,14 +53,16 @@ nav: - kops create: "cli/kops_create.md" - kops delete: "cli/kops_delete.md" - kops describe: "cli/kops_describe.md" + - kops distrust: "cli/kops_distrust.md" - kops edit: "cli/kops_edit.md" - kops export: "cli/kops_export.md" - kops get: "cli/kops_get.md" - - kops import: "cli/kops_import.md" + - kops promote: "cli/kops_promote.md" - kops replace: "cli/kops_replace.md" - kops rolling-update: "cli/kops_rolling-update.md" - kops set: "cli/kops_set.md" - kops toolbox: "cli/kops_toolbox.md" + - kops unset: "cli/kops_unset.md" - kops update: "cli/kops_update.md" - kops upgrade: "cli/kops_upgrade.md" - kops validate: "cli/kops_validate.md"