summaryrefslogtreecommitdiff
path: root/cmd/podman
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-11-17 12:42:06 +0100
committerGitHub <noreply@github.com>2020-11-17 12:42:06 +0100
commitd30f9ae8b6a21fbef6704e372e9ec74fc689875e (patch)
tree618af21a5e62ca8c62348f4159d675e11641cd7b /cmd/podman
parentc92bdf550c130cfe30fb545dc7bcf09f842444b4 (diff)
parentcf4967de4d4e4abeb183217dfee130d8ec2e02f5 (diff)
downloadpodman-d30f9ae8b6a21fbef6704e372e9ec74fc689875e.tar.gz
podman-d30f9ae8b6a21fbef6704e372e9ec74fc689875e.tar.bz2
podman-d30f9ae8b6a21fbef6704e372e9ec74fc689875e.zip
Merge pull request #8346 from Luap99/shell-completion
Improve the shell completion api
Diffstat (limited to 'cmd/podman')
-rw-r--r--cmd/podman/common/completion.go245
-rw-r--r--cmd/podman/containers/start.go9
2 files changed, 193 insertions, 61 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go
index 4d66b4e2b..00123f9e6 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -2,6 +2,7 @@ package common
import (
"bufio"
+ "fmt"
"os"
"strings"
@@ -22,62 +23,97 @@ var (
LogLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"}
)
-func getContainers(status string, toComplete string) ([]string, cobra.ShellCompDirective) {
+func getContainers(toComplete string, statuses ...string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOpts := entities.ContainerListOptions{
Filters: make(map[string][]string),
}
listOpts.All = true
+ listOpts.Pod = true
- if status != "all" {
- listOpts.Filters = map[string][]string{"status": {status}}
- }
+ // TODO: The api doesn't handle several different statuses correct see:
+ // https://github.com/containers/podman/issues/8344
+ // Instead of looping over the statuses we should be able to set
+ // listOpts.Filters["status"] = statuses
- containers, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
- if err != nil {
- cobra.CompErrorln(err.Error())
- return nil, cobra.ShellCompDirectiveError
+ var containers []entities.ListContainer
+ var err error
+ if len(statuses) == 0 {
+ containers, err = registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
+ if err != nil {
+ cobra.CompErrorln(err.Error())
+ return nil, cobra.ShellCompDirectiveError
+ }
+ } else {
+ for _, s := range statuses {
+ listOpts.Filters["status"] = []string{s}
+ res, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
+ if err != nil {
+ cobra.CompErrorln(err.Error())
+ return nil, cobra.ShellCompDirectiveError
+ }
+ containers = append(containers, res...)
+ }
}
- for _, container := range containers {
+ for _, c := range containers {
// include ids in suggestions if more then 2 chars are typed
- if len(toComplete) > 1 {
- suggestions = append(suggestions, container.ID[0:12])
+ if len(toComplete) > 1 && strings.HasPrefix(c.ID, toComplete) {
+ suggestions = append(suggestions, c.ID[0:12]+"\t"+c.PodName)
}
// include name in suggestions
- suggestions = append(suggestions, container.Names...)
+ if strings.HasPrefix(c.Names[0], toComplete) {
+ suggestions = append(suggestions, c.Names[0]+"\t"+c.PodName)
+ }
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
-func getPods(status string, toComplete string) ([]string, cobra.ShellCompDirective) {
+func getPods(toComplete string, statuses ...string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOpts := entities.PodPSOptions{
Filters: make(map[string][]string),
}
- if status != "all" {
- listOpts.Filters = map[string][]string{"status": {status}}
- }
+ // TODO: The api doesn't handle several different statuses correct see:
+ // https://github.com/containers/podman/issues/8344
+ // Instead of looping over the statuses we should be able to set
+ // listOpts.Filters["status"] = statuses
- pods, err := registry.ContainerEngine().PodPs(registry.GetContext(), listOpts)
- if err != nil {
- cobra.CompErrorln(err.Error())
- return nil, cobra.ShellCompDirectiveError
+ var pods []*entities.ListPodsReport
+ var err error
+ if len(statuses) == 0 {
+ pods, err = registry.ContainerEngine().PodPs(registry.GetContext(), listOpts)
+ if err != nil {
+ cobra.CompErrorln(err.Error())
+ return nil, cobra.ShellCompDirectiveError
+ }
+ } else {
+ for _, s := range statuses {
+ listOpts.Filters["status"] = []string{s}
+ res, err := registry.ContainerEngine().PodPs(registry.GetContext(), listOpts)
+ if err != nil {
+ cobra.CompErrorln(err.Error())
+ return nil, cobra.ShellCompDirectiveError
+ }
+ pods = append(pods, res...)
+ }
}
for _, pod := range pods {
// include ids in suggestions if more then 2 chars are typed
- if len(toComplete) > 1 {
+ if len(toComplete) > 1 && strings.HasPrefix(pod.Id, toComplete) {
suggestions = append(suggestions, pod.Id[0:12])
}
// include name in suggestions
- suggestions = append(suggestions, pod.Name)
+ if strings.HasPrefix(pod.Name, toComplete) {
+ suggestions = append(suggestions, pod.Name)
+ }
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
-func getVolumes() ([]string, cobra.ShellCompDirective) {
+func getVolumes(toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
lsOpts := entities.VolumeListOptions{}
@@ -87,8 +123,10 @@ func getVolumes() ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveError
}
- for _, volume := range volumes {
- suggestions = append(suggestions, volume.Name)
+ for _, v := range volumes {
+ if strings.HasPrefix(v.Name, toComplete) {
+ suggestions = append(suggestions, v.Name)
+ }
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
@@ -104,16 +142,16 @@ func getImages(toComplete string) ([]string, cobra.ShellCompDirective) {
}
for _, image := range images {
- // FIXME: need ux testing
- // discuss when image ids should be completed
// include ids in suggestions if more then 2 chars are typed
- if len(toComplete) > 1 {
+ if len(toComplete) > 1 && strings.HasPrefix(image.ID, toComplete) {
suggestions = append(suggestions, image.ID[0:12])
}
for _, repo := range image.RepoTags {
if toComplete == "" {
// suggest only full repo path if no input is given
- suggestions = append(suggestions, repo)
+ if strings.HasPrefix(repo, toComplete) {
+ suggestions = append(suggestions, repo)
+ }
} else {
// suggested "registry.fedoraproject.org/f29/httpd:latest" as
// - "registry.fedoraproject.org/f29/httpd:latest"
@@ -125,8 +163,13 @@ func getImages(toComplete string) ([]string, cobra.ShellCompDirective) {
paths := strings.Split(repo, "/")
for i := range paths {
suggestionWithTag := strings.Join(paths[i:], "/")
+ if strings.HasPrefix(suggestionWithTag, toComplete) {
+ suggestions = append(suggestions, suggestionWithTag)
+ }
suggestionWithoutTag := strings.SplitN(strings.SplitN(suggestionWithTag, ":", 2)[0], "@", 2)[0]
- suggestions = append(suggestions, suggestionWithTag, suggestionWithoutTag)
+ if strings.HasPrefix(suggestionWithoutTag, toComplete) {
+ suggestions = append(suggestions, suggestionWithoutTag)
+ }
}
}
}
@@ -143,9 +186,11 @@ func getRegistries() ([]string, cobra.ShellCompDirective) {
return regs, cobra.ShellCompDirectiveNoFileComp
}
-func getNetworks() ([]string, cobra.ShellCompDirective) {
+func getNetworks(toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
- networkListOptions := entities.NetworkListOptions{}
+ networkListOptions := entities.NetworkListOptions{
+ Filter: "name=" + toComplete,
+ }
networks, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil {
@@ -159,76 +204,154 @@ func getNetworks() ([]string, cobra.ShellCompDirective) {
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
+// validCurrentCmdLine validates the current cmd line
+// It utilizes the Args function from the cmd struct
+// In most cases the Args function validates the args length but it
+// is also used to verify that --latest is not given with an argument.
+// This function helps to makes sure we only complete valid arguments.
+func validCurrentCmdLine(cmd *cobra.Command, args []string, toComplete string) bool {
+ if cmd.Args == nil {
+ // Without an Args function we cannot check so assume it's correct
+ return true
+ }
+ // We have to append toComplete to the args otherwise the
+ // argument count would not match the expected behavior
+ if err := cmd.Args(cmd, append(args, toComplete)); err != nil {
+ // Special case if we use ExactArgs(2) or MinimumNArgs(2),
+ // They will error if we try to complete the first arg.
+ // Lets try to parse the common error and compare if we have less args than
+ // required. In this case we are fine and should provide completion.
+
+ // Clean the err msg so we can parse it with fmt.Sscanf
+ // Trim MinimumNArgs prefix
+ cleanErr := strings.TrimPrefix(err.Error(), "requires at least ")
+ // Trim MinimumNArgs "only" part
+ cleanErr = strings.ReplaceAll(cleanErr, "only received", "received")
+ // Trim ExactArgs prefix
+ cleanErr = strings.TrimPrefix(cleanErr, "accepts ")
+ var need, got int
+ cobra.CompDebugln(cleanErr, true)
+ _, err = fmt.Sscanf(cleanErr, "%d arg(s), received %d", &need, &got)
+ if err == nil {
+ if need >= got {
+ // We still need more arguments so provide more completions
+ return true
+ }
+ }
+ cobra.CompDebugln(err.Error(), true)
+ return false
+ }
+ return true
+}
+
/* Autocomplete Functions for cobra ValidArgsFunction */
// AutocompleteContainers - Autocomplete all container names.
func AutocompleteContainers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getContainers("all", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getContainers(toComplete)
}
// AutocompleteContainersCreated - Autocomplete only created container names.
func AutocompleteContainersCreated(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getContainers("created", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getContainers(toComplete, "created")
}
// AutocompleteContainersExited - Autocomplete only exited container names.
func AutocompleteContainersExited(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getContainers("exited", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getContainers(toComplete, "exited")
}
// AutocompleteContainersPaused - Autocomplete only paused container names.
func AutocompleteContainersPaused(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getContainers("paused", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getContainers(toComplete, "paused")
}
// AutocompleteContainersRunning - Autocomplete only running container names.
func AutocompleteContainersRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getContainers("running", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getContainers(toComplete, "running")
}
// AutocompleteContainersStartable - Autocomplete only created and exited container names.
func AutocompleteContainersStartable(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- containersCreated, _ := getContainers("created", toComplete)
- containersExited, _ := getContainers("exited", toComplete)
- return append(containersCreated, containersExited...), cobra.ShellCompDirectiveNoFileComp
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getContainers(toComplete, "created", "exited")
}
// AutocompletePods - Autocomplete all pod names.
func AutocompletePods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getPods("all", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getPods(toComplete)
}
// AutocompletePodsRunning - Autocomplete only running pod names.
+// It considers degraded as running.
func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getPods("running", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getPods(toComplete, "running", "degraded")
}
// AutocompleteContainersAndPods - Autocomplete container names and pod names.
func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- containers, _ := getContainers("all", toComplete)
- pods, _ := getPods("all", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ containers, _ := getContainers(toComplete)
+ pods, _ := getPods(toComplete)
return append(containers, pods...), cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteContainersAndImages - Autocomplete container names and pod names.
func AutocompleteContainersAndImages(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- containers, _ := getContainers("all", toComplete)
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ containers, _ := getContainers(toComplete)
images, _ := getImages(toComplete)
return append(containers, images...), cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteVolumes - Autocomplete volumes.
func AutocompleteVolumes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getVolumes()
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getVolumes(toComplete)
}
// AutocompleteImages - Autocomplete images.
func AutocompleteImages(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
return getImages(toComplete)
}
// AutocompleteCreateRun - Autocomplete only the fist argument as image and then do file completion.
func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
if len(args) < 1 {
return getImages(toComplete)
}
@@ -238,18 +361,27 @@ func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string)
// AutocompleteRegistries - Autocomplete registries.
func AutocompleteRegistries(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
return getRegistries()
}
// AutocompleteNetworks - Autocomplete networks.
func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return getNetworks()
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ return getNetworks(toComplete)
}
// AutocompleteCpCommand - Autocomplete podman cp command args.
func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
if len(args) < 2 {
- containers, _ := getContainers("all", toComplete)
+ containers, _ := getContainers(toComplete)
for _, container := range containers {
// TODO: Add path completion for inside the container if possible
if strings.HasPrefix(container, toComplete) {
@@ -265,6 +397,9 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string)
// AutocompleteSystemConnections - Autocomplete system connections.
func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
suggestions := []string{}
cfg, err := config.ReadCustomConfig()
if err != nil {
@@ -318,7 +453,7 @@ func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string)
switch {
case strings.HasPrefix(toComplete, "container:"):
// Complete containers after colon
- containers, _ := getContainers("all", toComplete[10:]) //trim "container:"
+ containers, _ := getContainers(toComplete[10:]) //trim "container:"
// add "container:" in front of the suggestions
var suggestions []string
@@ -504,21 +639,13 @@ func AutocompleteMountFlag(cmd *cobra.Command, args []string, toComplete string)
// AutocompleteVolumeFlag - Autocomplete volume flag options.
// -> volumes and paths
func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- result := []string{}
- volumes, _ := getVolumes()
- for _, volume := range volumes {
- // If we don't filter on "toComplete", zsh and fish will not do file completion
- // even if the prefix typed by the user does not match the returned completions
- if strings.HasPrefix(volume, toComplete) {
- result = append(result, volume)
- }
- }
+ volumes, _ := getVolumes(toComplete)
directive := cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveDefault
if strings.Contains(toComplete, ":") {
// add space after second path
directive = cobra.ShellCompDirectiveDefault
}
- return result, directive
+ return volumes, directive
}
// AutocompleteJSONFormat - Autocomplete format flag option.
diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go
index dba2c3c3e..7e57bb576 100644
--- a/cmd/podman/containers/start.go
+++ b/cmd/podman/containers/start.go
@@ -21,6 +21,7 @@ var (
Short: "Start one or more containers",
Long: startDescription,
RunE: start,
+ Args: validateStart,
ValidArgsFunction: common.AutocompleteContainersStartable,
Example: `podman start --latest
podman start 860a4b231279 5421ab43b45
@@ -32,6 +33,7 @@ var (
Short: startCommand.Short,
Long: startCommand.Long,
RunE: startCommand.RunE,
+ Args: startCommand.Args,
ValidArgsFunction: startCommand.ValidArgsFunction,
Example: `podman container start --latest
podman container start 860a4b231279 5421ab43b45
@@ -76,8 +78,7 @@ func init() {
validate.AddLatestFlag(containerStartCommand, &startOptions.Latest)
}
-func start(cmd *cobra.Command, args []string) error {
- var errs utils.OutputErrors
+func validateStart(cmd *cobra.Command, args []string) error {
if len(args) == 0 && !startOptions.Latest {
return errors.New("start requires at least one argument")
}
@@ -87,7 +88,11 @@ func start(cmd *cobra.Command, args []string) error {
if len(args) > 1 && startOptions.Attach {
return errors.Errorf("you cannot start and attach multiple containers at once")
}
+ return nil
+}
+func start(cmd *cobra.Command, args []string) error {
+ var errs utils.OutputErrors
sigProxy := startOptions.SigProxy || startOptions.Attach
if cmd.Flag("sig-proxy").Changed {
sigProxy = startOptions.SigProxy