Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement completion for "kops rolling-update cluster" #11924

Merged
merged 3 commits into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/kops/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cmd/kops/create_keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func NewCmdCreateKeypair(f *util.Factory, out io.Writer) *cobra.Command {
options := &CreateKeypairOptions{}

cmd := &cobra.Command{
Use: "keypair keyset",
Use: "keypair KEYSET",
Short: createKeypairShort,
Long: createKeypairLong,
Example: createKeypairExample,
Expand Down Expand Up @@ -270,7 +270,7 @@ func completeCreateKeyset(options *CreateKeypairOptions, args []string, toComple
commandutils.ConfigureKlogForCompletion()
ctx := context.TODO()

cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand)
cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand, "")
if cluster == nil {
return completions, directive
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/kops/distrust_keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func NewCmdDistrustKeypair(f *util.Factory, out io.Writer) *cobra.Command {
options := &DistrustKeypairOptions{}

cmd := &cobra.Command{
Use: "keypair keyset [id]...",
Use: "keypair KEYSET [ID]...",
Short: distrustKeypairShort,
Long: distrustKeypairLong,
Example: distrustKeypairExample,
Expand Down Expand Up @@ -161,7 +161,7 @@ func completeDistrustKeyset(options *DistrustKeypairOptions, args []string, toCo
commandutils.ConfigureKlogForCompletion()
ctx := context.TODO()

cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand)
cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand, "")
if cluster == nil {
return completions, directive
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/kops/get_keypairs.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewCmdGetKeypairs(f *util.Factory, out io.Writer, getOptions *GetOptions) *
GetOptions: getOptions,
}
cmd := &cobra.Command{
Use: "keypairs [keyset]...",
Use: "keypairs [KEYSET]...",
Aliases: []string{"keypair"},
Short: getKeypairShort,
Example: getKeypairExample,
Expand Down Expand Up @@ -197,7 +197,7 @@ func completeGetKeypairs(options *GetKeypairsOptions, args []string, toComplete
commandutils.ConfigureKlogForCompletion()
ctx := context.TODO()

cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand)
cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand, "")
if cluster == nil {
return completions, directive
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/kops/promote_keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func NewCmdPromoteKeypair(f *util.Factory, out io.Writer) *cobra.Command {
options := &PromoteKeypairOptions{}

cmd := &cobra.Command{
Use: "keypair keyset [id]",
Use: "keypair KEYSET [ID]",
Short: promoteKeypairShort,
Long: promoteKeypairLong,
Example: promoteKeypairExample,
Expand Down Expand Up @@ -167,7 +167,7 @@ func completePromoteKeyset(options *PromoteKeypairOptions, args []string, toComp
commandutils.ConfigureKlogForCompletion()
ctx := context.TODO()

cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand)
cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand, "")
if cluster == nil {
return completions, directive
}
Expand All @@ -193,7 +193,7 @@ func completePromoteKeyset(options *PromoteKeypairOptions, args []string, toComp
func completeKeypairID(keyset *fi.Keyset, filter func(keyset *fi.Keyset, item *fi.KeysetItem) bool) (completions []string, directive cobra.ShellCompDirective) {
for _, item := range keyset.Items {
if filter(keyset, item) {
completions = append(completions, item.Id)
completions = append(completions, fmt.Sprintf("%s\tissued %s", item.Id, item.Certificate.Certificate.NotBefore.Format("2006-01-02 15:04:05")))
}
}

Expand Down
6 changes: 2 additions & 4 deletions cmd/kops/rollingupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ import (

func NewCmdRollingUpdate(f *util.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "rolling-update",
Short: rollingupdateShort,
Long: rollingupdateLong,
Example: rollingupdateExample,
Use: "rolling-update",
Short: rollingupdateShort,
}

// create subcommands
Expand Down
147 changes: 80 additions & 67 deletions cmd/kops/rollingupdatecluster.go → cmd/kops/rollingupdate_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ import (
"time"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/kops/cmd/kops/util"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/cloudinstances"
"k8s.io/kops/pkg/commands/commandutils"
"k8s.io/kops/pkg/instancegroups"
"k8s.io/kops/pkg/pretty"
"k8s.io/kops/pkg/validation"
Expand All @@ -45,56 +48,44 @@ import (

var (
rollingupdateLong = pretty.LongDesc(i18n.T(`
This command updates a kubernetes cluster to match the cloud and kops specifications.
This command updates a kubernetes cluster to match the cloud and kOps specifications.

To perform a rolling update, you need to update the cloud resources first with the command
` + pretty.Bash("kops update cluster") + `. Nodes may be additionally marked for update by placing a
` + pretty.Bash("kops update cluster --yes") + `. Nodes may be additionally marked for update by placing a
` + pretty.Bash("kops.k8s.io/needs-update") + ` annotation on them.

If rolling-update does not report that the cluster needs to be rolled, you can force the cluster to be
rolled with the force flag. Rolling update drains and validates the cluster by default. A cluster is
If rolling-update does not report that the cluster needs to be updated, you can force the cluster to be
updated with the --force flag. Rolling update drains and validates the cluster by default. A cluster is
deemed validated when all required nodes are running and all pods with a critical priority are operational.
When a node is deleted, rolling-update sleeps the interval for the node type, and then tries for the same period
of time for the cluster to be validated. For instance, setting --master-interval=3m causes rolling-update
to wait for 3 minutes after a master is rolled, and another 3 minutes for the cluster to stabilize and pass
validation.

Note: terraform users will need to run all of the following commands from the same directory
` + pretty.Bash("kops update cluster --target=terraform") + ` then ` + pretty.Bash("terraform plan") + ` then
` + pretty.Bash("terraform apply") + ` prior to running ` + pretty.Bash("kops rolling-update cluster") + `.`))

rollingupdateExample = templates.Examples(i18n.T(`
# Preview a rolling-update.
# Preview a rolling update.
kops rolling-update cluster

# Roll the currently selected kOps cluster with defaults.
# Update the currently selected kOps cluster with defaults.
# Nodes will be drained and the cluster will be validated between node replacement.
kops rolling-update cluster --yes

# Roll the k8s-cluster.example.com kOps cluster,
# do not fail if the cluster does not validate,
# wait 8 min to create new node, and wait at least
# 8 min to validate the cluster.
# Update the k8s-cluster.example.com kOps cluster.
# Do not fail if the cluster does not validate.
kops rolling-update cluster k8s-cluster.example.com --yes \
--fail-on-validate-error="false" \
--master-interval=8m \
--node-interval=8m

# Roll the k8s-cluster.example.com kOps cluster,
# do not validate the cluster because of the cloudonly flag.
# Force the entire cluster to roll, even if rolling update
# reports that the cluster does not need to be rolled.
--fail-on-validate-error="false"

# Update the k8s-cluster.example.com kOps cluster.
# Do not validate the cluster.
# Force the entire cluster to update, even if rolling update
# reports that the cluster does not need to be updated.
kops rolling-update cluster k8s-cluster.example.com --yes \
--cloudonly \
--force

# Roll the k8s-cluster.example.com kOps cluster,
# only roll the node instancegroup,
# use the new drain and validate functionality.
# Update only the "nodes-1a" instance group of the k8s-cluster.example.com kOps cluster.
kops rolling-update cluster k8s-cluster.example.com --yes \
--fail-on-validate-error="false" \
--node-interval 8m \
--instance-group nodes
--instance-group nodes-1a
`))

rollingupdateShort = i18n.T(`Rolling update a cluster.`)
Expand Down Expand Up @@ -171,58 +162,52 @@ func NewCmdRollingUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command {
options.InitDefaults()

cmd := &cobra.Command{
Use: "cluster",
Short: rollingupdateShort,
Long: rollingupdateLong,
Example: rollingupdateExample,
Use: "cluster [CLUSTER]",
Short: rollingupdateShort,
Long: rollingupdateLong,
Example: rollingupdateExample,
Args: rootCommand.clusterNameArgs(&options.ClusterName),
ValidArgsFunction: commandutils.CompleteClusterName(&rootCommand, true),
RunE: func(cmd *cobra.Command, args []string) error {
return RunRollingUpdateCluster(context.TODO(), f, out, &options)
},
}

allRoles := make([]string, 0, len(kopsapi.AllInstanceGroupRoles))
for _, r := range kopsapi.AllInstanceGroupRoles {
allRoles = append(allRoles, string(r))
allRoles = append(allRoles, strings.ToLower(string(r)))
}

cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Perform rolling update immediately, without --yes rolling-update executes a dry-run")
cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Perform rolling update immediately; without --yes rolling-update executes a dry-run")
cmd.Flags().BoolVar(&options.Force, "force", options.Force, "Force rolling update, even if no changes")
cmd.Flags().BoolVar(&options.CloudOnly, "cloudonly", options.CloudOnly, "Perform rolling update without confirming progress with k8s")
cmd.Flags().BoolVar(&options.CloudOnly, "cloudonly", options.CloudOnly, "Perform rolling update without confirming progress with Kubernetes")

cmd.Flags().DurationVar(&options.ValidationTimeout, "validation-timeout", options.ValidationTimeout, "Maximum time to wait for a cluster to validate")
cmd.Flags().Int32Var(&options.ValidateCount, "validate-count", options.ValidateCount, "Amount of times that a cluster needs to be validated after single node update")
cmd.Flags().DurationVar(&options.MasterInterval, "master-interval", options.MasterInterval, "Time to wait between restarting masters")
cmd.Flags().DurationVar(&options.NodeInterval, "node-interval", options.NodeInterval, "Time to wait between restarting nodes")
cmd.Flags().Int32Var(&options.ValidateCount, "validate-count", options.ValidateCount, "Number of times that a cluster needs to be validated after single node update")
cmd.Flags().DurationVar(&options.MasterInterval, "master-interval", options.MasterInterval, "Time to wait between restarting control plane nodes")
cmd.Flags().DurationVar(&options.NodeInterval, "node-interval", options.NodeInterval, "Time to wait between restarting worker nodes")
cmd.Flags().DurationVar(&options.BastionInterval, "bastion-interval", options.BastionInterval, "Time to wait between restarting bastions")
cmd.Flags().DurationVar(&options.PostDrainDelay, "post-drain-delay", options.PostDrainDelay, "Time to wait after draining each node")
cmd.Flags().BoolVarP(&options.Interactive, "interactive", "i", options.Interactive, "Prompt to continue after each instance is updated")
cmd.Flags().StringSliceVar(&options.InstanceGroups, "instance-group", options.InstanceGroups, "List of instance groups to update (defaults to all if not specified)")
cmd.Flags().StringSliceVar(&options.InstanceGroupRoles, "instance-group-roles", options.InstanceGroupRoles, "If specified, only instance groups of the specified role will be updated ("+strings.Join(allRoles, ",")+")")

cmd.Flags().BoolVar(&options.FailOnDrainError, "fail-on-drain-error", true, "The rolling-update will fail if draining a node fails.")
cmd.Flags().BoolVar(&options.FailOnValidate, "fail-on-validate-error", true, "The rolling-update will fail if the cluster fails to validate.")

cmd.Run = func(cmd *cobra.Command, args []string) {
ctx := context.TODO()

err := rootCommand.ProcessArgs(args)
if err != nil {
exitWithError(err)
return
}

clusterName := rootCommand.ClusterName(true)
if clusterName == "" {
exitWithError(fmt.Errorf("--name is required"))
return
cmd.Flags().StringSliceVar(&options.InstanceGroups, "instance-group", options.InstanceGroups, "Instance groups to update (defaults to all if not specified)")
cmd.RegisterFlagCompletionFunc("instance-group", completeInstanceGroup(&options))
cmd.Flags().StringSliceVar(&options.InstanceGroupRoles, "instance-group-roles", options.InstanceGroupRoles, "Instance group roles to update ("+strings.Join(allRoles, ",")+")")
cmd.RegisterFlagCompletionFunc("instance-group-roles", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return sets.NewString(allRoles...).Delete(options.InstanceGroupRoles...).List(), cobra.ShellCompDirectiveNoFileComp
})

cmd.Flags().BoolVar(&options.FailOnDrainError, "fail-on-drain-error", true, "Fail if draining a node fails")
cmd.Flags().BoolVar(&options.FailOnValidate, "fail-on-validate-error", true, "Fail if the cluster fails to validate")

cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "ig", "instance-groups":
name = "instance-group"
case "role", "roles", "instance-group-role":
name = "instance-group-roles"
}

options.ClusterName = clusterName

err = RunRollingUpdateCluster(ctx, f, os.Stdout, &options)
if err != nil {
exitWithError(err)
return
}

}
return pflag.NormalizedName(name)
})

return cmd
}
Expand Down Expand Up @@ -443,3 +428,31 @@ func RunRollingUpdateCluster(ctx context.Context, f *util.Factory, out io.Writer

return d.RollingUpdate(groups, list)
}

func completeInstanceGroup(options *RollingUpdateOptions) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
commandutils.ConfigureKlogForCompletion()
ctx := context.TODO()

cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, &rootCommand, options.ClusterName)
if cluster == nil {
return completions, directive
}

list, err := clientSet.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{})
if err != nil {
return commandutils.CompletionError("listing instance groups", err)
}

alreadySelected := sets.NewString(options.InstanceGroups...)
alreadySelectedRoles := sets.NewString(options.InstanceGroupRoles...)
var igs []string
for _, ig := range list.Items {
if !alreadySelected.Has(ig.Name) && !alreadySelectedRoles.Has(strings.ToLower(string(ig.Spec.Role))) {
igs = append(igs, ig.Name)
}
}

return igs, cobra.ShellCompDirectiveNoFileComp
}
}
23 changes: 20 additions & 3 deletions cmd/kops/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func NewCmdRoot(f *util.Factory, out io.Writer) *cobra.Command {

defaultClusterName := os.Getenv("KOPS_CLUSTER_NAME")
cmd.PersistentFlags().StringVarP(&rootCommand.clusterName, "name", "", defaultClusterName, "Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable")
cmd.RegisterFlagCompletionFunc("name", commandutils.CompleteClusterName(&rootCommand))
cmd.RegisterFlagCompletionFunc("name", commandutils.CompleteClusterName(&rootCommand, false))

// create subcommands
cmd.AddCommand(NewCmdCreate(f, out))
Expand Down Expand Up @@ -200,6 +200,21 @@ func (c *RootCmd) AddCommand(cmd *cobra.Command) {
c.cobraCommand.AddCommand(cmd)
}

func (c *RootCmd) clusterNameArgs(clusterName *string) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
if err := c.ProcessArgs(args); err != nil {
return err
}

*clusterName = c.ClusterName(true)
if *clusterName == "" {
return fmt.Errorf("--name is required")
}

return nil
}
}

// ProcessArgs will parse the positional args. It assumes one of these formats:
// * <no arguments at all>
// * <clustername> (and --name not specified)
Expand Down Expand Up @@ -301,8 +316,10 @@ func GetCluster(ctx context.Context, factory commandutils.Factory, clusterName s
return cluster, nil
}

func GetClusterForCompletion(ctx context.Context, factory commandutils.Factory) (cluster *kopsapi.Cluster, clientSet simple.Clientset, completions []string, directive cobra.ShellCompDirective) {
clusterName := rootCommand.ClusterName(false)
func GetClusterForCompletion(ctx context.Context, factory commandutils.Factory, clusterName string) (cluster *kopsapi.Cluster, clientSet simple.Clientset, completions []string, directive cobra.ShellCompDirective) {
if clusterName == "" {
clusterName = rootCommand.ClusterName(false)
}

if clusterName == "" {
return nil, nil, []string{"--name"}, cobra.ShellCompDirectiveNoFileComp
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/kops_create_keypair.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/cli/kops_distrust_keypair.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/cli/kops_get_keypairs.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/cli/kops_promote_keypair.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading