Skip to content

Commit

Permalink
feat(cli): dynamic shell completion for main resources names (rollout…
Browse files Browse the repository at this point in the history
…s, experiments, analysisrun) (#2379)

* PoC dynamic shell completion for 'kubectl-argo-rollouts get rollout [TAB]'

works toward #2015

use ValidArgsFunction from cobra to list candidates for completion: https://github.com/spf13/cobra/blob/main/shell_completions.md#dynamic-completion-of-nouns

use v2 of GenBashCompletion from cobra: https://github.com/spf13/cobra/blob/main/shell_completions.md#bash-completion-v2
- I chose to disable descriptions for completion (as a bash user I'm not
  used to that), but it's enabled for fish it seems, we can enable it
  if desired, I have no strong opinion on it

Signed-off-by: Thomas Riccardi <[email protected]>

* Factorize resource names completion functions in new util/completion

Signed-off-by: Thomas Riccardi <[email protected]>

* Add dynamic auto-completion to all ROLLOUT_NAME locations in CLI

...accoding to the Cobra auto-generated CLI documentation.

Signed-off-by: Thomas Riccardi <[email protected]>

* Add dynamic auto-completion to all EXPERIMENT_NAME locations in CLI

...accoding to the Cobra auto-generated CLI documentation.

Signed-off-by: Thomas Riccardi <[email protected]>

* Add dynamic auto-completion to all ANALYSISRUN_NAME locations in CLI

...accoding to the Cobra auto-generated CLI documentation.

Signed-off-by: Thomas Riccardi <[email protected]>

* WIP unit-test dynamic completion

Signed-off-by: Thomas Riccardi <[email protected]>

* Add unit-tests for new completion package

- inspired from kubectl own completion package tests
- fake cmd to avoid circular import
- reuse info/testdata but we could build our own data for more
  isolated tests if needed
- test all 3 types (Rollout, Experiment, AnalysisRun)
- test both first argument completion and second argument

Signed-off-by: Thomas Riccardi <[email protected]>

* Revert "WIP unit-test dynamic completion"

Like in kubectl own completion tests, we now have an independent
'completion' package with its own tests. Stop testing in cmd/ callers
packages.

This reverts commit 75eb5ae.

Signed-off-by: Thomas Riccardi <[email protected]>

Signed-off-by: Thomas Riccardi <[email protected]>
  • Loading branch information
thomas-riccardi authored Nov 8, 2022
1 parent 8d668c5 commit 3f02c92
Show file tree
Hide file tree
Showing 14 changed files with 241 additions and 1 deletion.
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/abort/abort.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/typed/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
)

const (
Expand Down Expand Up @@ -52,6 +53,7 @@ func NewCmdAbort(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
return cmd
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl-argo-rollouts/cmd/completion/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewCmdCompletion(o *options.ArgoRolloutsOptions) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(o.Out)
cmd.Root().GenBashCompletionV2(o.Out, false)
case "zsh":
cmd.Root().GenZshCompletion(o.Out)
case "fish":
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/get/get_experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/apiclient/rollout"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/info"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/viewcontroller"
)

Expand Down Expand Up @@ -64,6 +65,7 @@ func NewCmdGetExperiment(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.ExperimentNameCompletionFunc(o),
}
cmd.Flags().BoolVarP(&getOptions.Watch, "watch", "w", false, "Watch live updates to the rollout")
cmd.Flags().BoolVar(&getOptions.NoColor, "no-color", false, "Do not colorize output")
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/get/get_rollout.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/cmd/signals"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/info"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/viewcontroller"
)

Expand Down Expand Up @@ -75,6 +76,7 @@ func NewCmdGetRollout(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
cmd.Flags().BoolVarP(&getOptions.Watch, "watch", "w", false, "Watch live updates to the rollout")
cmd.Flags().BoolVar(&getOptions.NoColor, "no-color", false, "Do not colorize output")
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/pause/pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
types "k8s.io/apimachinery/pkg/types"

"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
)

const (
Expand Down Expand Up @@ -40,6 +41,7 @@ func NewCmdPause(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
return cmd
}
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/promote/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/typed/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset"
)

Expand Down Expand Up @@ -77,6 +78,7 @@ func NewCmdPromote(o *options.ArgoRolloutsOptions) *cobra.Command {

return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
cmd.Flags().BoolVarP(&skipCurrentStep, "skip-current-step", "c", false, "Skip currently running canary step")
cmd.Flags().BoolVarP(&skipAllSteps, "skip-all-steps", "a", false, "Skip remaining steps")
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/restart/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/typed/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
timeutil "github.com/argoproj/argo-rollouts/utils/time"
)

Expand Down Expand Up @@ -62,6 +63,7 @@ func NewCmdRestart(o *options.ArgoRolloutsOptions) *cobra.Command {
fmt.Fprintf(o.Out, "rollout '%s' restarts in %s\n", ro.Name, in)
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
cmd.Flags().StringVarP(&in, "in", "i", "", "Amount of time before a restart. (e.g. 30s, 5m, 1h)")
return cmd
Expand Down
3 changes: 3 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/retry/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/typed/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
)

const (
Expand Down Expand Up @@ -76,6 +77,7 @@ func NewCmdRetryRollout(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
return cmd
}
Expand Down Expand Up @@ -115,6 +117,7 @@ func NewCmdRetryExperiment(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.ExperimentNameCompletionFunc(o),
}
return cmd
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/set/set_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
)

const (
Expand Down Expand Up @@ -60,6 +61,7 @@ func NewCmdSetImage(o *options.ArgoRolloutsOptions) *cobra.Command {
fmt.Fprintf(o.Out, "%s \"%s\" image updated\n", strings.ToLower(un.GetKind()), un.GetName())
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
return cmd
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/argoproj/argo-rollouts/pkg/apiclient/rollout"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/cmd/signals"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/viewcontroller"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -89,6 +90,7 @@ func NewCmdStatus(o *options.ArgoRolloutsOptions) *cobra.Command {

return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
cmd.Flags().BoolVarP(&statusOptions.Watch, "watch", "w", true, "Watch the status of the rollout until it's done")
cmd.Flags().DurationVarP(&statusOptions.Timeout, "timeout", "t", time.Duration(0), "The length of time to watch before giving up. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). Zero means wait forever")
Expand Down
3 changes: 3 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/terminate/terminate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
types "k8s.io/apimachinery/pkg/types"

"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
)

const (
Expand Down Expand Up @@ -73,6 +74,7 @@ func NewCmdTerminateAnalysisRun(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.AnalysisRunNameCompletionFunc(o),
}
return cmd
}
Expand Down Expand Up @@ -101,6 +103,7 @@ func NewCmdTerminateExperiment(o *options.ArgoRolloutsOptions) *cobra.Command {
}
return nil
},
ValidArgsFunction: completionutil.ExperimentNameCompletionFunc(o),
}
return cmd
}
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/undo/undo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
completionutil "github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/util/completion"
routils "github.com/argoproj/argo-rollouts/utils/unstructured"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -60,6 +61,7 @@ func NewCmdUndo(o *options.ArgoRolloutsOptions) *cobra.Command {
fmt.Fprintf(o.Out, result)
return nil
},
ValidArgsFunction: completionutil.RolloutNameCompletionFunc(o),
}
cmd.Flags().Int64Var(&toRevision, "to-revision", toRevision, "The revision to rollback to. Default to 0 (last revision).")
return cmd
Expand Down
91 changes: 91 additions & 0 deletions pkg/kubectl-argo-rollouts/util/completion/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package completion

import (
"strings"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
)

// RolloutNameCompletionFunc Returns a completion function that completes as a first argument
// the Rollouts names that match the toComplete prefix.
func RolloutNameCompletionFunc(o *options.ArgoRolloutsOptions) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// list Rollouts names
ctx := c.Context()
opts := metav1.ListOptions{}
rolloutIf := o.RolloutsClientset().ArgoprojV1alpha1().Rollouts(o.Namespace())
rolloutList, err := rolloutIf.List(ctx, opts)
if err != nil {
return []string{}, cobra.ShellCompDirectiveError
}

var rolloutNames []string
for _, ro := range rolloutList.Items {
if strings.HasPrefix(ro.Name, toComplete) {
rolloutNames = append(rolloutNames, ro.Name)
}
}

return rolloutNames, cobra.ShellCompDirectiveNoFileComp
}
}

// ExperimentNameCompletionFunc Returns a completion function that completes as a first argument
// the Experiments names that match the toComplete prefix.
func ExperimentNameCompletionFunc(o *options.ArgoRolloutsOptions) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// list Experiments names
ctx := c.Context()
opts := metav1.ListOptions{}
expIf := o.RolloutsClientset().ArgoprojV1alpha1().Experiments(o.Namespace())
expList, err := expIf.List(ctx, opts)
if err != nil {
return []string{}, cobra.ShellCompDirectiveError
}

var expNames []string
for _, exp := range expList.Items {
if strings.HasPrefix(exp.Name, toComplete) {
expNames = append(expNames, exp.Name)
}
}

return expNames, cobra.ShellCompDirectiveNoFileComp
}
}

// AnalysisRunNameCompletionFunc Returns a completion function that completes as a first argument
// the AnalysisRuns names that match the toComplete prefix.
func AnalysisRunNameCompletionFunc(o *options.ArgoRolloutsOptions) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// list AnalysisRuns names
ctx := c.Context()
opts := metav1.ListOptions{}
arIf := o.RolloutsClientset().ArgoprojV1alpha1().AnalysisRuns(o.Namespace())
arList, err := arIf.List(ctx, opts)
if err != nil {
return []string{}, cobra.ShellCompDirectiveError
}

var arNames []string
for _, ar := range arList.Items {
if strings.HasPrefix(ar.Name, toComplete) {
arNames = append(arNames, ar.Name)
}
}

return arNames, cobra.ShellCompDirectiveNoFileComp
}
}
Loading

0 comments on commit 3f02c92

Please sign in to comment.