diff options
Diffstat (limited to 'cmd/podman')
131 files changed, 4032 insertions, 1472 deletions
diff --git a/cmd/podman/auto-update.go b/cmd/podman/auto-update.go index 76bff0c70..cf806900f 100644 --- a/cmd/podman/auto-update.go +++ b/cmd/podman/auto-update.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/errorhandling" @@ -20,10 +21,11 @@ var ( or similar units that create new containers in order to run the updated images. Please refer to the podman-auto-update(1) man page for details.` autoUpdateCommand = &cobra.Command{ - Use: "auto-update [options]", - Short: "Auto update containers according to their auto-update policy", - Long: autoUpdateDescription, - RunE: autoUpdate, + Use: "auto-update [options]", + Short: "Auto update containers according to their auto-update policy", + Long: autoUpdateDescription, + RunE: autoUpdate, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman auto-update podman auto-update --authfile ~/authfile.json`, } @@ -36,7 +38,10 @@ func init() { }) flags := autoUpdateCommand.Flags() - flags.StringVar(&autoUpdateOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path to the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + + authfileFlagName := "authfile" + flags.StringVar(&autoUpdateOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path to the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = autoUpdateCommand.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) } func autoUpdate(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go new file mode 100644 index 000000000..9856e46ef --- /dev/null +++ b/cmd/podman/common/completion.go @@ -0,0 +1,941 @@ +package common + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/registries" + "github.com/containers/podman/v2/pkg/rootless" + systemdGen "github.com/containers/podman/v2/pkg/systemd/generate" + "github.com/containers/podman/v2/pkg/util" + "github.com/spf13/cobra" +) + +var ( + // ChangeCmds is the list of valid Change commands to passed to the Commit call + ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"} + // LogLevels supported by podman + LogLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"} +) + +type completeType int + +const ( + // complete names and IDs after two chars + completeDefault completeType = iota + // only complete IDs + completeIDs + // only complete Names + completeNames +) + +type keyValueCompletion map[string]func(s string) ([]string, cobra.ShellCompDirective) + +func setupContainerEngine(cmd *cobra.Command) (entities.ContainerEngine, error) { + containerEngine, err := registry.NewContainerEngine(cmd, []string{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, err + } + if !registry.IsRemote() && rootless.IsRootless() { + err := containerEngine.SetupRootless(registry.Context(), cmd) + if err != nil { + return nil, err + } + } + return containerEngine, nil +} + +func setupImageEngine(cmd *cobra.Command) (entities.ImageEngine, error) { + imageEngine, err := registry.NewImageEngine(cmd, []string{}) + if err != nil { + return nil, err + } + // we also need to set up the container engine since this + // is required to setup the rootless namespace + if _, err = setupContainerEngine(cmd); err != nil { + return nil, err + } + return imageEngine, nil +} + +func getContainers(cmd *cobra.Command, toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + listOpts := entities.ContainerListOptions{ + Filters: make(map[string][]string), + } + listOpts.All = true + listOpts.Pod = true + if len(statuses) > 0 { + listOpts.Filters["status"] = statuses + } + + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + containers, err := engine.ContainerList(registry.GetContext(), listOpts) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for _, c := range containers { + // include ids in suggestions if cType == completeIDs or + // more then 2 chars are typed and cType == completeDefault + if ((len(toComplete) > 1 && cType == completeDefault) || + cType == completeIDs) && strings.HasPrefix(c.ID, toComplete) { + suggestions = append(suggestions, c.ID[0:12]+"\t"+c.PodName) + } + // include name in suggestions + if cType != completeIDs && strings.HasPrefix(c.Names[0], toComplete) { + suggestions = append(suggestions, c.Names[0]+"\t"+c.PodName) + } + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getPods(cmd *cobra.Command, toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + listOpts := entities.PodPSOptions{ + Filters: make(map[string][]string), + } + if len(statuses) > 0 { + listOpts.Filters["status"] = statuses + } + + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + pods, err := engine.PodPs(registry.GetContext(), listOpts) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for _, pod := range pods { + // include ids in suggestions if cType == completeIDs or + // more then 2 chars are typed and cType == completeDefault + if ((len(toComplete) > 1 && cType == completeDefault) || + cType == completeIDs) && strings.HasPrefix(pod.Id, toComplete) { + suggestions = append(suggestions, pod.Id[0:12]) + } + // include name in suggestions + if cType != completeIDs && strings.HasPrefix(pod.Name, toComplete) { + suggestions = append(suggestions, pod.Name) + } + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getVolumes(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + lsOpts := entities.VolumeListOptions{} + + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + volumes, err := engine.VolumeList(registry.GetContext(), lsOpts) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for _, v := range volumes { + if strings.HasPrefix(v.Name, toComplete) { + suggestions = append(suggestions, v.Name) + } + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getImages(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + listOptions := entities.ImageListOptions{} + + engine, err := setupImageEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + images, err := engine.List(registry.GetContext(), listOptions) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for _, image := range images { + // include ids in suggestions if more then 2 chars are typed + 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 + if strings.HasPrefix(repo, toComplete) { + suggestions = append(suggestions, repo) + } + } else { + // suggested "registry.fedoraproject.org/f29/httpd:latest" as + // - "registry.fedoraproject.org/f29/httpd:latest" + // - "registry.fedoraproject.org/f29/httpd" + // - "f29/httpd:latest" + // - "f29/httpd" + // - "httpd:latest" + // - "httpd" + 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] + if strings.HasPrefix(suggestionWithoutTag, toComplete) { + suggestions = append(suggestions, suggestionWithoutTag) + } + } + } + } + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getRegistries() ([]string, cobra.ShellCompDirective) { + regs, err := registries.GetRegistries() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + return regs, cobra.ShellCompDirectiveNoFileComp +} + +func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + networkListOptions := entities.NetworkListOptions{} + + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + networks, err := engine.NetworkList(registry.Context(), networkListOptions) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for _, n := range networks { + if strings.HasPrefix(n.Name, toComplete) { + suggestions = append(suggestions, n.Name) + } + } + 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 +} + +func prefixSlice(pre string, slice []string) []string { + for i := range slice { + slice[i] = pre + slice[i] + } + return slice +} + +func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra.ShellCompDirective) { + suggestions := make([]string, 0, len(k)) + directive := cobra.ShellCompDirectiveNoFileComp + for key, getComps := range k { + if strings.HasPrefix(toComplete, key) { + if getComps != nil { + suggestions, dir := getComps(toComplete[len(key):]) + return prefixSlice(key, suggestions), dir + } + return nil, cobra.ShellCompDirectiveNoFileComp + } + if strings.HasPrefix(key, toComplete) { + suggestions = append(suggestions, key) + latKey := key[len(key)-1:] + if latKey == "=" || latKey == ":" { + // make sure we don't add a space after ':' or '=' + directive = cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp + } + } + } + return suggestions, directive +} + +/* Autocomplete Functions for cobra ValidArgsFunction */ + +// AutocompleteContainers - Autocomplete all container names. +func AutocompleteContainers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getContainers(cmd, toComplete, completeDefault) +} + +// AutocompleteContainersCreated - Autocomplete only created container names. +func AutocompleteContainersCreated(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getContainers(cmd, toComplete, completeDefault, "created") +} + +// AutocompleteContainersExited - Autocomplete only exited container names. +func AutocompleteContainersExited(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getContainers(cmd, toComplete, completeDefault, "exited") +} + +// AutocompleteContainersPaused - Autocomplete only paused container names. +func AutocompleteContainersPaused(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getContainers(cmd, toComplete, completeDefault, "paused") +} + +// AutocompleteContainersRunning - Autocomplete only running container names. +func AutocompleteContainersRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getContainers(cmd, toComplete, completeDefault, "running") +} + +// AutocompleteContainersStartable - Autocomplete only created and exited container names. +func AutocompleteContainersStartable(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getContainers(cmd, toComplete, completeDefault, "created", "exited") +} + +// AutocompletePods - Autocomplete all pod names. +func AutocompletePods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getPods(cmd, toComplete, completeDefault) +} + +// AutocompletePodsRunning - Autocomplete only running pod names. +// It considers degraded as running. +func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getPods(cmd, toComplete, completeDefault, "running", "degraded") +} + +// AutocompleteContainersAndPods - Autocomplete container names and pod names. +func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + containers, _ := getContainers(cmd, toComplete, completeDefault) + pods, _ := getPods(cmd, toComplete, completeDefault) + 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) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + containers, _ := getContainers(cmd, toComplete, completeDefault) + images, _ := getImages(cmd, toComplete) + return append(containers, images...), cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteVolumes - Autocomplete volumes. +func AutocompleteVolumes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getVolumes(cmd, 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(cmd, 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(cmd, toComplete) + } + // TODO: add path completion for files in the image + return nil, cobra.ShellCompDirectiveDefault +} + +// 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) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getNetworks(cmd, 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(cmd, toComplete, completeDefault) + for _, container := range containers { + // TODO: Add path completion for inside the container if possible + if strings.HasPrefix(container, toComplete) { + return containers, cobra.ShellCompDirectiveNoSpace + } + } + // else complete paths + return nil, cobra.ShellCompDirectiveDefault + } + // don't complete more than 2 args + return nil, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteNetworkConnectCmd - Autocomplete podman network connect/disconnect command args. +func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return getNetworks(cmd, toComplete) + } + if len(args) == 1 { + return getContainers(cmd, toComplete, completeDefault) + } + // don't complete more than 2 args + return nil, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteTopCmd - Autocomplete podman top/pod top command args. +func AutocompleteTopCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + latest := cmd.Flags().Lookup("latest") + // only complete containers/pods as first arg if latest is not set + if len(args) == 0 && (latest == nil || !latest.Changed) { + if cmd.Parent().Name() == "pod" { + // need to complete pods since we are using pod top + return getPods(cmd, toComplete, completeDefault) + } + return getContainers(cmd, toComplete, completeDefault) + } + descriptors, err := util.GetContainerPidInformationDescriptors() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + return descriptors, cobra.ShellCompDirectiveNoFileComp +} + +// 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 { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for k, v := range cfg.Engine.ServiceDestinations { + // the URI will be show as description in shells like zsh + suggestions = append(suggestions, k+"\t"+v.URI) + } + + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +/* -------------- Flags ----------------- */ + +// AutocompleteDetachKeys - Autocomplete detach-keys options. +// -> "ctrl-" +func AutocompleteDetachKeys(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if strings.HasSuffix(toComplete, ",") { + return []string{toComplete + "ctrl-"}, cobra.ShellCompDirectiveNoSpace + } + return []string{"ctrl-"}, cobra.ShellCompDirectiveNoSpace +} + +// AutocompleteChangeInstructions - Autocomplete change instructions options for commit and import. +// -> "CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR" +func AutocompleteChangeInstructions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return ChangeCmds, cobra.ShellCompDirectiveNoSpace +} + +// AutocompleteImageFormat - Autocomplete image format options. +// -> "oci", "docker" +func AutocompleteImageFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ImageFormat := []string{"oci", "docker"} + return ImageFormat, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteCreateAttach - Autocomplete create --attach options. +// -> "stdin", "stdout", "stderr" +func AutocompleteCreateAttach(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"stdin", "stdout", "stderr"}, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteNamespace - Autocomplete namespace options. +// -> host,container:[name],ns:[path],private +func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "ns:": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault }, + "host": nil, + "private": nil, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteUserNamespace - Autocomplete namespace options. +// -> same as AutocompleteNamespace with "auto", "keep-id" added +func AutocompleteUserNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + results, directive := AutocompleteNamespace(cmd, args, toComplete) + results = append(results, "auto", "keep-id") + return results, directive +} + +// AutocompleteCgroupMode - Autocomplete cgroup mode options. +// -> "enabled", "disabled", "no-conmon", "split" +func AutocompleteCgroupMode(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cgroupModes := []string{"enabled", "disabled", "no-conmon", "split"} + return cgroupModes, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteImageVolume - Autocomplete image volume options. +// -> "bind", "tmpfs", "ignore" +func AutocompleteImageVolume(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + imageVolumes := []string{"bind", "tmpfs", "ignore"} + return imageVolumes, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteLogDriver - Autocomplete log-driver options. +// -> "journald", "none", "k8s-file" +func AutocompleteLogDriver(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // don't show json-file + logDrivers := []string{define.JournaldLogging, define.NoLogging, define.KubernetesLogging} + return logDrivers, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteLogOpt - Autocomplete log-opt options. +// -> "path=", "tag=" +func AutocompleteLogOpt(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // FIXME: are these the only one? the man page states these but in the current shell completion they are more options + logOptions := []string{"path=", "tag="} + if strings.HasPrefix(toComplete, "path=") { + return nil, cobra.ShellCompDirectiveDefault + } + return logOptions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace +} + +// AutocompletePullOption - Autocomplete pull options for create and run command. +// -> "always", "missing", "never" +func AutocompletePullOption(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + pullOptions := []string{"always", "missing", "never"} + return pullOptions, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteRestartOption - Autocomplete restart options for create and run command. +// -> "always", "no", "on-failure", "unless-stopped" +func AutocompleteRestartOption(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + restartOptions := []string{libpod.RestartPolicyAlways, libpod.RestartPolicyNo, + libpod.RestartPolicyOnFailure, libpod.RestartPolicyUnlessStopped} + return restartOptions, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteSecurityOption - Autocomplete security options options. +func AutocompleteSecurityOption(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "apparmor=": nil, + "no-new-privileges": nil, + "seccomp=": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault }, + "label=": func(s string) ([]string, cobra.ShellCompDirective) { + if strings.HasPrefix(s, "d") { + return []string{"disable"}, cobra.ShellCompDirectiveNoFileComp + } + return []string{"user:", "role:", "type:", "level:", "filetype:", "disable"}, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp + }, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteStopSignal - Autocomplete stop signal options. +// -> "SIGHUP", "SIGINT", "SIGKILL", "SIGTERM" +func AutocompleteStopSignal(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // FIXME: add more/different signals? + stopSignals := []string{"SIGHUP", "SIGINT", "SIGKILL", "SIGTERM"} + return stopSignals, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteSystemdFlag - Autocomplete systemd flag options. +// -> "true", "false", "always" +func AutocompleteSystemdFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + systemd := []string{"true", "false", "always"} + return systemd, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteUserFlag - Autocomplete user flag based on the names and groups (includes ids after first char) in /etc/passwd and /etc/group files. +// -> user:group +func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if strings.Contains(toComplete, ":") { + // It would be nice to read the file in the image + // but at this point we don't know the image. + file, err := os.Open("/etc/group") + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + defer file.Close() + + var groups []string + scanner := bufio.NewScanner(file) + user := strings.SplitN(toComplete, ":", 2)[0] + for scanner.Scan() { + entries := strings.SplitN(scanner.Text(), ":", 4) + groups = append(groups, user+":"+entries[0]) + // complete ids after at least one char is given + if len(user)+1 < len(toComplete) { + groups = append(groups, user+":"+entries[2]) + } + } + if err = scanner.Err(); err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + return groups, cobra.ShellCompDirectiveNoFileComp + } + + // It would be nice to read the file in the image + // but at this point we don't know the image. + file, err := os.Open("/etc/passwd") + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + defer file.Close() + + var users []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + entries := strings.SplitN(scanner.Text(), ":", 7) + users = append(users, entries[0]+":") + // complete ids after at least one char is given + if len(toComplete) > 0 { + users = append(users, entries[2]+":") + } + } + if err = scanner.Err(); err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return users, cobra.ShellCompDirectiveNoSpace +} + +// AutocompleteMountFlag - Autocomplete mount flag options. +// -> "type=bind,", "type=volume,", "type=tmpfs," +func AutocompleteMountFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"type=bind,", "type=volume,", "type=tmpfs,"} + // TODO: Add support for all different options + return types, cobra.ShellCompDirectiveNoSpace +} + +// AutocompleteVolumeFlag - Autocomplete volume flag options. +// -> volumes and paths +func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + volumes, _ := getVolumes(cmd, toComplete) + directive := cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveDefault + if strings.Contains(toComplete, ":") { + // add space after second path + directive = cobra.ShellCompDirectiveDefault + } + return volumes, directive +} + +// AutocompleteJSONFormat - Autocomplete format flag option. +// -> "json" +func AutocompleteJSONFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"json"}, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteEventFilter - Autocomplete event filter flag options. +// -> "container=", "event=", "image=", "pod=", "volume=", "type=" +func AutocompleteEventFilter(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + eventTypes := func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"attach", "checkpoint", "cleanup", "commit", "create", "exec", + "export", "import", "init", "kill", "mount", "pause", "prune", "remove", + "restart", "restore", "start", "stop", "sync", "unmount", "unpause", + "pull", "push", "save", "tag", "untag", "refresh", "renumber", + }, cobra.ShellCompDirectiveNoFileComp + } + kv := keyValueCompletion{ + "container=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "image=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) }, + "pod=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeDefault) }, + "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "event=": eventTypes, + "type=": eventTypes, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteSystemdRestartOptions - Autocomplete systemd restart options. +// -> "no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always" +func AutocompleteSystemdRestartOptions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return systemdGen.RestartPolicies, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteTrustType - Autocomplete trust type options. +// -> "signedBy", "accept", "reject" +func AutocompleteTrustType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"signedBy", "accept", "reject"} + return types, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteImageSort - Autocomplete images sort options. +// -> "created", "id", "repository", "size", "tag" +func AutocompleteImageSort(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + sortBy := []string{"created", "id", "repository", "size", "tag"} + return sortBy, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteInspectType - Autocomplete inspect type options. +// -> "container", "image", "all" +func AutocompleteInspectType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"container", "image", "all"} + return types, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteManifestFormat - Autocomplete manifest format options. +// -> "oci", "v2s2" +func AutocompleteManifestFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"oci", "v2s2"} + return types, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteNetworkDriver - Autocomplete network driver option. +// -> "bridge" +func AutocompleteNetworkDriver(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + drivers := []string{"bridge"} + return drivers, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompletePodShareNamespace - Autocomplete pod create --share flag option. +// -> "ipc", "net", "pid", "user", "uts", "cgroup", "none" +func AutocompletePodShareNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + namespaces := []string{"ipc", "net", "pid", "user", "uts", "cgroup", "none"} + return namespaces, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompletePodPsSort - Autocomplete images sort options. +// -> "created", "id", "name", "status", "number" +func AutocompletePodPsSort(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + sortBy := []string{"created", "id", "name", "status", "number"} + return sortBy, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompletePsSort - Autocomplete images sort options. +// -> "command", "created", "id", "image", "names", "runningfor", "size", "status" +func AutocompletePsSort(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + sortBy := []string{"command", "created", "id", "image", "names", "runningfor", "size", "status"} + return sortBy, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteImageSaveFormat - Autocomplete image save format options. +// -> "oci-archive", "oci-dir", "docker-dir" +func AutocompleteImageSaveFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + formats := []string{"oci-archive", "oci-dir", "docker-dir"} + return formats, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteWaitCondition - Autocomplete wait condition options. +// -> "unknown", "configured", "created", "running", "stopped", "paused", "exited", "removing" +func AutocompleteWaitCondition(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + states := []string{"unknown", "configured", "created", "running", "stopped", "paused", "exited", "removing"} + return states, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteCgroupManager - Autocomplete cgroup manager options. +// -> "cgroupfs", "systemd" +func AutocompleteCgroupManager(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"cgroupfs", "systemd"} + return types, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteEventBackend - Autocomplete event backend options. +// -> "file", "journald", "none" +func AutocompleteEventBackend(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"file", "journald", "none"} + return types, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteLogLevel - Autocomplete log level options. +// -> "debug", "info", "warn", "error", "fatal", "panic" +func AutocompleteLogLevel(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return LogLevels, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteSDNotify - Autocomplete sdnotify options. +// -> "container", "conmon", "ignore" +func AutocompleteSDNotify(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"container", "conmon", "ignore"} + return types, cobra.ShellCompDirectiveNoFileComp +} + +var containerStatuses = []string{"created", "running", "paused", "stopped", "exited", "unknown"} + +// AutocompletePsFilters - Autocomplete ps filter options. +func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeIDs) }, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) }, + "status=": func(_ string) ([]string, cobra.ShellCompDirective) { + return containerStatuses, cobra.ShellCompDirectiveNoFileComp + }, + "ancestor": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) }, + "before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "health=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{define.HealthCheckHealthy, + define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp + }, + "label=": nil, + "exited=": nil, + "until=": nil, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompletePodPsFilters - Autocomplete pod ps filter options. +func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeIDs) }, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeNames) }, + "status=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"stopped", "running", + "paused", "exited", "dead", "created", "degraded"}, cobra.ShellCompDirectiveNoFileComp + }, + "ctr-ids=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeIDs) }, + "ctr-names=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) }, + "ctr-number=": nil, + "ctr-status=": func(_ string) ([]string, cobra.ShellCompDirective) { + return containerStatuses, cobra.ShellCompDirectiveNoFileComp + }, + "label=": nil, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteImageFilters - Autocomplete image ls --filter options. +func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + getBool := func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp + } + getImg := func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) } + kv := keyValueCompletion{ + "before=": getImg, + "since=": getImg, + "label=": nil, + "reference=": nil, + "dangling=": getBool, + "readonly=": getBool, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteNetworkFilters - Autocomplete network ls --filter options. +func AutocompleteNetworkFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, + "plugin=": nil, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteVolumeFilters - Autocomplete volume ls --filter options. +func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + local := func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"local"}, cobra.ShellCompDirectiveNoFileComp + } + kv := keyValueCompletion{ + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "driver=": local, + "scope=": local, + "label=": nil, + "opt=": nil, + "dangling=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp + }, + } + return completeKeyValues(toComplete, kv) +} diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 60f4e526c..599b430ea 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -5,296 +5,457 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/libpod/define" - "github.com/spf13/pflag" + "github.com/spf13/cobra" ) const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))" var containerConfig = registry.PodmanConfig() -func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { - createFlags := pflag.FlagSet{} +func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { + createFlags := cmd.Flags() + + annotationFlagName := "annotation" createFlags.StringSliceVar( &cf.Annotation, - "annotation", []string{}, + annotationFlagName, []string{}, "Add annotations to container (key:value)", ) + _ = cmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) + + attachFlagName := "attach" createFlags.StringSliceVarP( &cf.Attach, - "attach", "a", []string{}, + attachFlagName, "a", []string{}, "Attach to STDIN, STDOUT or STDERR", ) + _ = cmd.RegisterFlagCompletionFunc(attachFlagName, AutocompleteCreateAttach) + + authfileFlagName := "authfile" createFlags.StringVar( &cf.Authfile, - "authfile", auth.GetDefaultAuthFile(), + authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override", ) + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + + blkioWeightFlagName := "blkio-weight" createFlags.StringVar( &cf.BlkIOWeight, - "blkio-weight", "", + blkioWeightFlagName, "", "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", ) + _ = cmd.RegisterFlagCompletionFunc(blkioWeightFlagName, completion.AutocompleteNone) + + blkioWeightDeviceFlagName := "blkio-weight-device" createFlags.StringSliceVar( &cf.BlkIOWeightDevice, - "blkio-weight-device", []string{}, + blkioWeightDeviceFlagName, []string{}, "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", ) + _ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault) + + capAddFlagName := "cap-add" createFlags.StringSliceVar( &cf.CapAdd, - "cap-add", []string{}, + capAddFlagName, []string{}, "Add capabilities to the container", ) + _ = cmd.RegisterFlagCompletionFunc(capAddFlagName, completion.AutocompleteCapabilities) + + capDropFlagName := "cap-drop" createFlags.StringSliceVar( &cf.CapDrop, - "cap-drop", []string{}, + capDropFlagName, []string{}, "Drop capabilities from the container", ) + _ = cmd.RegisterFlagCompletionFunc(capDropFlagName, completion.AutocompleteCapabilities) + + cgroupnsFlagName := "cgroupns" createFlags.String( - "cgroupns", "", + cgroupnsFlagName, "", "cgroup namespace to use", ) + _ = cmd.RegisterFlagCompletionFunc(cgroupnsFlagName, AutocompleteNamespace) + + cgroupsFlagName := "cgroups" createFlags.StringVar( &cf.CGroupsMode, - "cgroups", containerConfig.Cgroups(), + cgroupsFlagName, cgroupConfig(), `control container cgroup configuration ("enabled"|"disabled"|"no-conmon"|"split")`, ) + _ = cmd.RegisterFlagCompletionFunc(cgroupsFlagName, AutocompleteCgroupMode) + + cgroupParentFlagName := "cgroup-parent" createFlags.StringVar( &cf.CGroupParent, - "cgroup-parent", "", + cgroupParentFlagName, "", "Optional parent cgroup for the container", ) + _ = cmd.RegisterFlagCompletionFunc(cgroupParentFlagName, completion.AutocompleteDefault) + + cidfileFlagName := "cidfile" createFlags.StringVar( &cf.CIDFile, - "cidfile", "", + cidfileFlagName, "", "Write the container ID to the file", ) + _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) + + conmonPidfileFlagName := "conmon-pidfile" createFlags.StringVar( &cf.ConmonPIDFile, - "conmon-pidfile", "", + conmonPidfileFlagName, "", "Path to the file that will receive the PID of conmon", ) + _ = cmd.RegisterFlagCompletionFunc(conmonPidfileFlagName, completion.AutocompleteDefault) + + cpuPeriodFlagName := "cpu-period" createFlags.Uint64Var( &cf.CPUPeriod, - "cpu-period", 0, + cpuPeriodFlagName, 0, "Limit the CPU CFS (Completely Fair Scheduler) period", ) + _ = cmd.RegisterFlagCompletionFunc(cpuPeriodFlagName, completion.AutocompleteNone) + + cpuQuotaFlagName := "cpu-quota" createFlags.Int64Var( &cf.CPUQuota, - "cpu-quota", 0, + cpuQuotaFlagName, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota", ) + _ = cmd.RegisterFlagCompletionFunc(cpuQuotaFlagName, completion.AutocompleteNone) + + cpuRtPeriodFlagName := "cpu-rt-period" createFlags.Uint64Var( &cf.CPURTPeriod, - "cpu-rt-period", 0, + cpuRtPeriodFlagName, 0, "Limit the CPU real-time period in microseconds", ) + _ = cmd.RegisterFlagCompletionFunc(cpuRtPeriodFlagName, completion.AutocompleteNone) + + cpuRtRuntimeFlagName := "cpu-rt-runtime" createFlags.Int64Var( &cf.CPURTRuntime, - "cpu-rt-runtime", 0, + cpuRtRuntimeFlagName, 0, "Limit the CPU real-time runtime in microseconds", ) + _ = cmd.RegisterFlagCompletionFunc(cpuRtRuntimeFlagName, completion.AutocompleteNone) + + cpuSharesFlagName := "cpu-shares" createFlags.Uint64Var( &cf.CPUShares, - "cpu-shares", 0, + cpuSharesFlagName, 0, "CPU shares (relative weight)", ) + _ = cmd.RegisterFlagCompletionFunc(cpuSharesFlagName, completion.AutocompleteNone) + + cpusFlagName := "cpus" createFlags.Float64Var( &cf.CPUS, - "cpus", 0, + cpusFlagName, 0, "Number of CPUs. The default is 0.000 which means no limit", ) + _ = cmd.RegisterFlagCompletionFunc(cpusFlagName, completion.AutocompleteNone) + + cpusetCpusFlagName := "cpuset-cpus" createFlags.StringVar( &cf.CPUSetCPUs, - "cpuset-cpus", "", + cpusetCpusFlagName, "", "CPUs in which to allow execution (0-3, 0,1)", ) + _ = cmd.RegisterFlagCompletionFunc(cpusetCpusFlagName, completion.AutocompleteNone) + + cpusetMemsFlagName := "cpuset-mems" createFlags.StringVar( &cf.CPUSetMems, - "cpuset-mems", "", + cpusetMemsFlagName, "", "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", ) + _ = cmd.RegisterFlagCompletionFunc(cpusetMemsFlagName, completion.AutocompleteNone) + + deviceFlagName := "device" createFlags.StringSliceVar( &cf.Devices, - "device", containerConfig.Devices(), + deviceFlagName, devices(), fmt.Sprintf("Add a host device to the container"), ) + _ = cmd.RegisterFlagCompletionFunc(deviceFlagName, completion.AutocompleteDefault) + + deviceCgroupRuleFlagName := "device-cgroup-rule" createFlags.StringSliceVar( &cf.DeviceCGroupRule, - "device-cgroup-rule", []string{}, + deviceCgroupRuleFlagName, []string{}, "Add a rule to the cgroup allowed devices list", ) + _ = cmd.RegisterFlagCompletionFunc(deviceCgroupRuleFlagName, completion.AutocompleteNone) + + deviceReadBpsFlagName := "device-read-bps" createFlags.StringSliceVar( &cf.DeviceReadBPs, - "device-read-bps", []string{}, + deviceReadBpsFlagName, []string{}, "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", ) + _ = cmd.RegisterFlagCompletionFunc(deviceReadBpsFlagName, completion.AutocompleteDefault) + + deviceReadIopsFlagName := "device-read-iops" createFlags.StringSliceVar( &cf.DeviceReadIOPs, - "device-read-iops", []string{}, + deviceReadIopsFlagName, []string{}, "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", ) + _ = cmd.RegisterFlagCompletionFunc(deviceReadIopsFlagName, completion.AutocompleteDefault) + + deviceWriteBpsFlagName := "device-write-bps" createFlags.StringSliceVar( &cf.DeviceWriteBPs, - "device-write-bps", []string{}, + deviceWriteBpsFlagName, []string{}, "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", ) + _ = cmd.RegisterFlagCompletionFunc(deviceWriteBpsFlagName, completion.AutocompleteDefault) + + deviceWriteIopsFlagName := "device-write-iops" createFlags.StringSliceVar( &cf.DeviceWriteIOPs, - "device-write-iops", []string{}, + deviceWriteIopsFlagName, []string{}, "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", ) + _ = cmd.RegisterFlagCompletionFunc(deviceWriteIopsFlagName, completion.AutocompleteDefault) + createFlags.Bool( "disable-content-trust", false, "This is a Docker specific option and is a NOOP", ) - createFlags.String("entrypoint", "", + + entrypointFlagName := "entrypoint" + createFlags.String(entrypointFlagName, "", "Overwrite the default ENTRYPOINT of the image", ) + _ = cmd.RegisterFlagCompletionFunc(entrypointFlagName, completion.AutocompleteNone) + + envFlagName := "env" createFlags.StringArrayP( - "env", "e", containerConfig.Env(), + envFlagName, "e", env(), "Set environment variables in container", ) + _ = cmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) + if !registry.IsRemote() { createFlags.BoolVar( &cf.EnvHost, "env-host", false, "Use all current host environment variables in container", ) } + + envFileFlagName := "env-file" createFlags.StringSliceVar( &cf.EnvFile, - "env-file", []string{}, + envFileFlagName, []string{}, "Read in a file of environment variables", ) + _ = cmd.RegisterFlagCompletionFunc(envFileFlagName, completion.AutocompleteDefault) + + exposeFlagName := "expose" createFlags.StringSliceVar( &cf.Expose, - "expose", []string{}, + exposeFlagName, []string{}, "Expose a port or a range of ports", ) + _ = cmd.RegisterFlagCompletionFunc(exposeFlagName, completion.AutocompleteNone) + + gidmapFlagName := "gidmap" createFlags.StringSliceVar( &cf.GIDMap, - "gidmap", []string{}, + gidmapFlagName, []string{}, "GID map to use for the user namespace", ) + _ = cmd.RegisterFlagCompletionFunc(gidmapFlagName, completion.AutocompleteNone) + + groupAddFlagName := "group-add" createFlags.StringSliceVar( &cf.GroupAdd, - "group-add", []string{}, + groupAddFlagName, []string{}, "Add additional groups to join", ) + _ = cmd.RegisterFlagCompletionFunc(groupAddFlagName, completion.AutocompleteNone) + createFlags.Bool( "help", false, "", ) + + healthCmdFlagName := "health-cmd" createFlags.StringVar( &cf.HealthCmd, - "health-cmd", "", + healthCmdFlagName, "", "set a healthcheck command for the container ('none' disables the existing healthcheck)", ) + _ = cmd.RegisterFlagCompletionFunc(healthCmdFlagName, completion.AutocompleteNone) + + healthIntervalFlagName := "health-interval" createFlags.StringVar( &cf.HealthInterval, - "health-interval", DefaultHealthCheckInterval, + healthIntervalFlagName, DefaultHealthCheckInterval, "set an interval for the healthchecks (a value of disable results in no automatic timer setup)", ) + _ = cmd.RegisterFlagCompletionFunc(healthIntervalFlagName, completion.AutocompleteNone) + + healthRetriesFlagName := "health-retries" createFlags.UintVar( &cf.HealthRetries, - "health-retries", DefaultHealthCheckRetries, + healthRetriesFlagName, DefaultHealthCheckRetries, "the number of retries allowed before a healthcheck is considered to be unhealthy", ) + _ = cmd.RegisterFlagCompletionFunc(healthRetriesFlagName, completion.AutocompleteNone) + + healthStartPeriodFlagName := "health-start-period" createFlags.StringVar( &cf.HealthStartPeriod, - "health-start-period", DefaultHealthCheckStartPeriod, + healthStartPeriodFlagName, DefaultHealthCheckStartPeriod, "the initialization time needed for a container to bootstrap", ) + _ = cmd.RegisterFlagCompletionFunc(healthStartPeriodFlagName, completion.AutocompleteNone) + + healthTimeoutFlagName := "health-timeout" createFlags.StringVar( &cf.HealthTimeout, - "health-timeout", DefaultHealthCheckTimeout, + healthTimeoutFlagName, DefaultHealthCheckTimeout, "the maximum time allowed to complete the healthcheck before an interval is considered failed", ) + _ = cmd.RegisterFlagCompletionFunc(healthTimeoutFlagName, completion.AutocompleteNone) + + hostnameFlagName := "hostname" createFlags.StringVarP( &cf.Hostname, - "hostname", "h", "", + hostnameFlagName, "h", "", "Set container hostname", ) + _ = cmd.RegisterFlagCompletionFunc(hostnameFlagName, completion.AutocompleteNone) + createFlags.BoolVar( &cf.HTTPProxy, "http-proxy", true, "Set proxy environment variables in the container based on the host proxy vars", ) + + imageVolumeFlagName := "image-volume" createFlags.StringVar( &cf.ImageVolume, - "image-volume", DefaultImageVolume, + imageVolumeFlagName, DefaultImageVolume, `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, ) + _ = cmd.RegisterFlagCompletionFunc(imageVolumeFlagName, AutocompleteImageVolume) + createFlags.BoolVar( &cf.Init, "init", false, "Run an init binary inside the container that forwards signals and reaps processes", ) + + initPathFlagName := "init-path" createFlags.StringVar( &cf.InitPath, - "init-path", containerConfig.InitPath(), + initPathFlagName, initPath(), // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) fmt.Sprintf("Path to the container-init binary"), ) + _ = cmd.RegisterFlagCompletionFunc(initPathFlagName, completion.AutocompleteDefault) + createFlags.BoolVarP( &cf.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached", ) + + ipcFlagName := "ipc" createFlags.String( - "ipc", "", + ipcFlagName, "", "IPC namespace to use", ) + _ = cmd.RegisterFlagCompletionFunc(ipcFlagName, AutocompleteNamespace) + + kernelMemoryFlagName := "kernel-memory" createFlags.StringVar( &cf.KernelMemory, - "kernel-memory", "", + kernelMemoryFlagName, "", "Kernel memory limit "+sizeWithUnitFormat, ) + _ = cmd.RegisterFlagCompletionFunc(kernelMemoryFlagName, completion.AutocompleteNone) + + labelFlagName := "label" createFlags.StringArrayVarP( &cf.Label, - "label", "l", []string{}, + labelFlagName, "l", []string{}, "Set metadata on container", ) + _ = cmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone) + + labelFileFlagName := "label-file" createFlags.StringSliceVar( &cf.LabelFile, - "label-file", []string{}, + labelFileFlagName, []string{}, "Read in a line delimited file of labels", ) + _ = cmd.RegisterFlagCompletionFunc(labelFileFlagName, completion.AutocompleteDefault) + + logDriverFlagName := "log-driver" createFlags.StringVar( &cf.LogDriver, - "log-driver", "", + logDriverFlagName, "", "Logging driver for the container", ) + _ = cmd.RegisterFlagCompletionFunc(logDriverFlagName, AutocompleteLogDriver) + + logOptFlagName := "log-opt" createFlags.StringSliceVar( &cf.LogOptions, - "log-opt", []string{}, + logOptFlagName, []string{}, "Logging driver options", ) + _ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt) + + memoryFlagName := "memory" createFlags.StringVarP( &cf.Memory, - "memory", "m", "", + memoryFlagName, "m", "", "Memory limit "+sizeWithUnitFormat, ) + _ = cmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone) + + memoryReservationFlagName := "memory-reservation" createFlags.StringVar( &cf.MemoryReservation, - "memory-reservation", "", + memoryReservationFlagName, "", "Memory soft limit "+sizeWithUnitFormat, ) + _ = cmd.RegisterFlagCompletionFunc(memoryReservationFlagName, completion.AutocompleteNone) + + memorySwapFlagName := "memory-swap" createFlags.StringVar( &cf.MemorySwap, - "memory-swap", "", + memorySwapFlagName, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", ) + _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone) + + memorySwappinessFlagName := "memory-swappiness" createFlags.Int64Var( &cf.MemorySwappiness, - "memory-swappiness", -1, + memorySwappinessFlagName, -1, "Tune container memory swappiness (0 to 100, or -1 for system default)", ) + _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone) + + nameFlagName := "name" createFlags.StringVar( &cf.Name, - "name", "", + nameFlagName, "", "Assign a name to the container", ) + _ = cmd.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) + createFlags.BoolVar( &cf.NoHealthCheck, "no-healthcheck", false, @@ -305,44 +466,69 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "oom-kill-disable", false, "Disable OOM Killer", ) + + oomScoreAdjFlagName := "oom-score-adj" createFlags.IntVar( &cf.OOMScoreAdj, - "oom-score-adj", 0, + oomScoreAdjFlagName, 0, "Tune the host's OOM preferences (-1000 to 1000)", ) + _ = cmd.RegisterFlagCompletionFunc(oomScoreAdjFlagName, completion.AutocompleteNone) + + overrideArchFlagName := "override-arch" createFlags.StringVar( &cf.OverrideArch, - "override-arch", "", + overrideArchFlagName, "", "use `ARCH` instead of the architecture of the machine for choosing images", ) + _ = cmd.RegisterFlagCompletionFunc(overrideArchFlagName, completion.AutocompleteNone) + + overrideOSFlagName := "override-os" createFlags.StringVar( &cf.OverrideOS, - "override-os", "", + overrideOSFlagName, "", "use `OS` instead of the running OS for choosing images", ) + _ = cmd.RegisterFlagCompletionFunc(overrideOSFlagName, completion.AutocompleteNone) + + overrideVariantFlagName := "override-variant" createFlags.StringVar( &cf.OverrideVariant, - "override-variant", "", + overrideVariantFlagName, "", "Use _VARIANT_ instead of the running architecture variant for choosing images", ) + _ = cmd.RegisterFlagCompletionFunc(overrideVariantFlagName, completion.AutocompleteNone) + + pidFlagName := "pid" createFlags.String( - "pid", "", + pidFlagName, "", "PID namespace to use", ) + _ = cmd.RegisterFlagCompletionFunc(pidFlagName, AutocompleteNamespace) + + pidsLimitFlagName := "pids-limit" createFlags.Int64( - "pids-limit", containerConfig.PidsLimit(), + pidsLimitFlagName, pidsLimit(), "Tune container pids limit (set 0 for unlimited, -1 for server defaults)", ) + _ = cmd.RegisterFlagCompletionFunc(pidsLimitFlagName, completion.AutocompleteNone) + + podFlagName := "pod" createFlags.StringVar( &cf.Pod, - "pod", "", + podFlagName, "", "Run container in an existing pod", ) + _ = cmd.RegisterFlagCompletionFunc(podFlagName, AutocompletePods) + + podIDFileFlagName := "pod-id-file" createFlags.StringVar( &cf.PodIDFile, - "pod-id-file", "", + podIDFileFlagName, "", "Read the pod ID from the file", ) + _ = cmd.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) + createFlags.BoolVar( &cf.Privileged, "privileged", false, @@ -353,11 +539,15 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "publish-all", "P", false, "Publish all exposed ports to random ports on the host interface", ) + + pullFlagName := "pull" createFlags.StringVar( &cf.Pull, - "pull", containerConfig.Engine.PullPolicy, + pullFlagName, policy(), `Pull image before creating ("always"|"missing"|"never")`, ) + _ = cmd.RegisterFlagCompletionFunc(pullFlagName, AutocompletePullOption) + createFlags.BoolVarP( &cf.Quiet, "quiet", "q", false, @@ -378,11 +568,15 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "replace", false, `If a container with the same name exists, replace it`, ) + + restartFlagName := "restart" createFlags.StringVar( &cf.Restart, - "restart", "", + restartFlagName, "", `Restart policy to apply when a container exits ("always"|"no"|"on-failure"|"unless-stopped")`, ) + _ = cmd.RegisterFlagCompletionFunc(restartFlagName, AutocompleteRestartOption) + createFlags.BoolVar( &cf.Rm, "rm", false, @@ -393,20 +587,31 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "rootfs", false, "The first argument is not an image but the rootfs to the exploded container", ) + + sdnotifyFlagName := "sdnotify" createFlags.StringVar( &cf.SdNotifyMode, - "sdnotify", define.SdNotifyModeContainer, + sdnotifyFlagName, define.SdNotifyModeContainer, `control sd-notify behavior ("container"|"conmon"|"ignore")`, ) + _ = cmd.RegisterFlagCompletionFunc(sdnotifyFlagName, AutocompleteSDNotify) + + securityOptFlagName := "security-opt" createFlags.StringArrayVar( &cf.SecurityOpt, - "security-opt", []string{}, + securityOptFlagName, []string{}, "Security Options", ) + _ = cmd.RegisterFlagCompletionFunc(securityOptFlagName, AutocompleteSecurityOption) + + shmSizeFlagName := "shm-size" createFlags.String( - "shm-size", containerConfig.ShmSize(), + shmSizeFlagName, shmSize(), "Size of /dev/shm "+sizeWithUnitFormat, ) + _ = cmd.RegisterFlagCompletionFunc(shmSizeFlagName, completion.AutocompleteNone) + + stopSignalFlagName := "stop-signal" createFlags.StringVar( &cf.SignaturePolicy, "signature-policy", "", @@ -414,112 +619,175 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringVar( &cf.StopSignal, - "stop-signal", "", + stopSignalFlagName, "", "Signal to stop a container. Default is SIGTERM", ) + _ = cmd.RegisterFlagCompletionFunc(stopSignalFlagName, AutocompleteStopSignal) + + stopTimeoutFlagName := "stop-timeout" createFlags.UintVar( &cf.StopTimeout, - "stop-timeout", containerConfig.Engine.StopTimeout, + stopTimeoutFlagName, containerConfig.Engine.StopTimeout, "Timeout (in seconds) to stop a container. Default is 10", ) + _ = cmd.RegisterFlagCompletionFunc(stopTimeoutFlagName, completion.AutocompleteNone) + + storageOptFlagName := "storage-opt" createFlags.StringSliceVar( &cf.StoreageOpt, - "storage-opt", []string{}, + storageOptFlagName, []string{}, "Storage driver options per container", ) + //FIXME: What should we suggest here? The flag is not in the man page. + _ = cmd.RegisterFlagCompletionFunc(storageOptFlagName, completion.AutocompleteNone) + + subgidnameFlagName := "subgidname" createFlags.StringVar( &cf.SubUIDName, - "subgidname", "", + subgidnameFlagName, "", "Name of range listed in /etc/subgid for use in user namespace", ) + _ = cmd.RegisterFlagCompletionFunc(subgidnameFlagName, completion.AutocompleteSubgidName) + + subuidnameFlagName := "subuidname" createFlags.StringVar( &cf.SubGIDName, - "subuidname", "", + subuidnameFlagName, "", "Name of range listed in /etc/subuid for use in user namespace", ) + _ = cmd.RegisterFlagCompletionFunc(subuidnameFlagName, completion.AutocompleteSubuidName) + sysctlFlagName := "sysctl" createFlags.StringSliceVar( &cf.Sysctl, - "sysctl", []string{}, + sysctlFlagName, []string{}, "Sysctl options", ) + //TODO: Add function for systctl completion. + _ = cmd.RegisterFlagCompletionFunc(sysctlFlagName, completion.AutocompleteNone) + + systemdFlagName := "systemd" createFlags.StringVar( &cf.Systemd, - "systemd", "true", + systemdFlagName, "true", `Run container in systemd mode ("true"|"false"|"always")`, ) + _ = cmd.RegisterFlagCompletionFunc(systemdFlagName, AutocompleteSystemdFlag) + + tmpfsFlagName := "tmpfs" createFlags.StringArrayVar( &cf.TmpFS, - "tmpfs", []string{}, + tmpfsFlagName, []string{}, "Mount a temporary filesystem (`tmpfs`) into a container", ) + _ = cmd.RegisterFlagCompletionFunc(tmpfsFlagName, completion.AutocompleteDefault) + createFlags.BoolVarP( &cf.TTY, "tty", "t", false, "Allocate a pseudo-TTY for container", ) + + timezonezFlagName := "tz" createFlags.StringVar( &cf.Timezone, - "tz", containerConfig.TZ(), + timezonezFlagName, containerConfig.TZ(), "Set timezone in container", ) + _ = cmd.RegisterFlagCompletionFunc(timezonezFlagName, completion.AutocompleteNone) //TODO: add timezone completion + + umaskFlagName := "umask" createFlags.StringVar( &cf.Umask, - "umask", containerConfig.Umask(), + umaskFlagName, containerConfig.Umask(), "Set umask in container", ) + _ = cmd.RegisterFlagCompletionFunc(umaskFlagName, completion.AutocompleteNone) + + uidmapFlagName := "uidmap" createFlags.StringSliceVar( &cf.UIDMap, - "uidmap", []string{}, + uidmapFlagName, []string{}, "UID map to use for the user namespace", ) + _ = cmd.RegisterFlagCompletionFunc(uidmapFlagName, completion.AutocompleteNone) + + ulimitFlagName := "ulimit" createFlags.StringSliceVar( &cf.Ulimit, - "ulimit", containerConfig.Ulimits(), + ulimitFlagName, ulimits(), "Ulimit options", ) + _ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone) + + userFlagName := "user" createFlags.StringVarP( &cf.User, - "user", "u", "", + userFlagName, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])", ) + _ = cmd.RegisterFlagCompletionFunc(userFlagName, AutocompleteUserFlag) + + usernsFlagName := "userns" createFlags.String( - "userns", os.Getenv("PODMAN_USERNS"), + usernsFlagName, os.Getenv("PODMAN_USERNS"), "User namespace to use", ) + _ = cmd.RegisterFlagCompletionFunc(usernsFlagName, AutocompleteUserNamespace) + + utsFlagName := "uts" createFlags.String( - "uts", "", + utsFlagName, "", "UTS namespace to use", ) + _ = cmd.RegisterFlagCompletionFunc(utsFlagName, AutocompleteNamespace) + + mountFlagName := "mount" createFlags.StringArrayVar( &cf.Mount, - "mount", []string{}, + mountFlagName, []string{}, "Attach a filesystem mount to the container", ) + _ = cmd.RegisterFlagCompletionFunc(mountFlagName, AutocompleteMountFlag) + + volumeFlagName := "volume" createFlags.StringArrayVarP( &cf.Volume, - "volume", "v", containerConfig.Volumes(), + volumeFlagName, "v", volumes(), "Bind mount a volume into the container", ) + _ = cmd.RegisterFlagCompletionFunc(volumeFlagName, AutocompleteVolumeFlag) + + volumesFromFlagName := "volumes-from" createFlags.StringArrayVar( &cf.VolumesFrom, - "volumes-from", []string{}, + volumesFromFlagName, []string{}, "Mount volumes from the specified container(s)", ) + _ = cmd.RegisterFlagCompletionFunc(volumesFromFlagName, AutocompleteContainers) + + workdirFlagName := "workdir" createFlags.StringVarP( &cf.Workdir, - "workdir", "w", "", + workdirFlagName, "w", "", "Working directory inside the container", ) + _ = cmd.RegisterFlagCompletionFunc(workdirFlagName, completion.AutocompleteDefault) + + seccompPolicyFlagName := "seccomp-policy" createFlags.StringVar( &cf.SeccompPolicy, - "seccomp-policy", "default", + seccompPolicyFlagName, "default", "Policy for selecting a seccomp profile (experimental)", ) + _ = cmd.RegisterFlagCompletionFunc(seccompPolicyFlagName, completion.AutocompleteDefault) + + cgroupConfFlagName := "cgroup-conf" createFlags.StringSliceVar( &cf.CgroupConf, - "cgroup-conf", []string{}, + cgroupConfFlagName, []string{}, "Configure cgroup v2 (key=value)", ) - return &createFlags + _ = cmd.RegisterFlagCompletionFunc(cgroupConfFlagName, completion.AutocompleteNone) + } diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 05bb9de13..6dc43dbc6 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -6,8 +6,11 @@ import ( "strconv" "strings" + "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/api/handlers" + "github.com/containers/podman/v2/pkg/cgroups" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/specgen" ) @@ -129,11 +132,11 @@ func stringMaptoArray(m map[string]string) []string { // ContainerCreateToContainerCLIOpts converts a compat input struct to cliopts so it can be converted to // a specgen spec. -func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*ContainerCLIOpts, []string, error) { +func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroupsManager string) (*ContainerCLIOpts, []string, error) { var ( capAdd []string cappDrop []string - entrypoint string + entrypoint *string init bool specPorts []specgen.PortMapping ) @@ -177,13 +180,14 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont // marshall it to json; otherwise it should just be the string // value if len(cc.Config.Entrypoint) > 0 { - entrypoint = cc.Config.Entrypoint[0] + entrypoint = &cc.Config.Entrypoint[0] if len(cc.Config.Entrypoint) > 1 { b, err := json.Marshal(cc.Config.Entrypoint) if err != nil { return nil, nil, err } - entrypoint = string(b) + var jsonString = string(b) + entrypoint = &jsonString } } @@ -207,7 +211,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont mounts = append(mounts, mount) } - //volumes + // volumes volumes := make([]string, 0, len(cc.Config.Volumes)) for v := range cc.Config.Volumes { volumes = append(volumes, v) @@ -237,13 +241,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont } } - // network names - endpointsConfig := cc.NetworkingConfig.EndpointsConfig - cniNetworks := make([]string, 0, len(endpointsConfig)) - for netName := range endpointsConfig { - cniNetworks = append(cniNetworks, netName) - } - // netMode nsmode, _, err := specgen.ParseNetworkNamespace(cc.HostConfig.NetworkMode.NetworkName()) if err != nil { @@ -260,7 +257,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont // defined when there is only one network. netInfo := entities.NetOptions{ AddHosts: cc.HostConfig.ExtraHosts, - CNINetworks: cniNetworks, DNSOptions: cc.HostConfig.DNSOptions, DNSSearch: cc.HostConfig.DNSSearch, DNSServers: dns, @@ -268,31 +264,58 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont PublishPorts: specPorts, } - // static IP and MAC - if len(endpointsConfig) == 1 { - for _, ep := range endpointsConfig { - // if IP address is provided - if len(ep.IPAddress) > 0 { - staticIP := net.ParseIP(ep.IPAddress) - netInfo.StaticIP = &staticIP + // network names + switch { + case len(cc.NetworkingConfig.EndpointsConfig) > 0: + var aliases []string + + endpointsConfig := cc.NetworkingConfig.EndpointsConfig + cniNetworks := make([]string, 0, len(endpointsConfig)) + for netName, endpoint := range endpointsConfig { + + cniNetworks = append(cniNetworks, netName) + + if endpoint == nil { + continue + } + if len(endpoint.Aliases) > 0 { + aliases = append(aliases, endpoint.Aliases...) } - // If MAC address is provided - if len(ep.MacAddress) > 0 { - staticMac, err := net.ParseMAC(ep.MacAddress) - if err != nil { - return nil, nil, err + } + + // static IP and MAC + if len(endpointsConfig) == 1 { + for _, ep := range endpointsConfig { + if ep == nil { + continue + } + // if IP address is provided + if len(ep.IPAddress) > 0 { + staticIP := net.ParseIP(ep.IPAddress) + netInfo.StaticIP = &staticIP + } + // If MAC address is provided + if len(ep.MacAddress) > 0 { + staticMac, err := net.ParseMAC(ep.MacAddress) + if err != nil { + return nil, nil, err + } + netInfo.StaticMAC = &staticMac } - netInfo.StaticMAC = &staticMac + break } - break } + netInfo.Aliases = aliases + netInfo.CNINetworks = cniNetworks + case len(cc.HostConfig.NetworkMode) > 0: + netInfo.CNINetworks = []string{string(cc.HostConfig.NetworkMode)} } // Note: several options here are marked as "don't need". this is based // on speculation by Matt and I. We think that these come into play later // like with start. We believe this is just a difference in podman/compat cliOpts := ContainerCLIOpts{ - //Attach: nil, // dont need? + // Attach: nil, // dont need? Authfile: "", CapAdd: append(capAdd, cc.HostConfig.CapAdd...), CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), @@ -303,18 +326,18 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, CPUShares: uint64(cc.HostConfig.CPUShares), - //CPUS: 0, // dont need? + // CPUS: 0, // dont need? CPUSetCPUs: cc.HostConfig.CpusetCpus, CPUSetMems: cc.HostConfig.CpusetMems, - //Detach: false, // dont need - //DetachKeys: "", // dont need + // Detach: false, // dont need + // DetachKeys: "", // dont need Devices: devices, DeviceCGroupRule: nil, DeviceReadBPs: readBps, DeviceReadIOPs: readIops, DeviceWriteBPs: writeBps, DeviceWriteIOPs: writeIops, - Entrypoint: &entrypoint, + Entrypoint: entrypoint, Env: cc.Config.Env, Expose: expose, GroupAdd: cc.HostConfig.GroupAdd, @@ -346,16 +369,23 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont Systemd: "true", // podman default TmpFS: stringMaptoArray(cc.HostConfig.Tmpfs), TTY: cc.Config.Tty, - //Ulimit: cc.HostConfig.Ulimits, // ask dan, no documented format - Ulimit: []string{"nproc=4194304:4194304"}, - User: cc.Config.User, - UserNS: string(cc.HostConfig.UsernsMode), - UTS: string(cc.HostConfig.UTSMode), - Mount: mounts, - Volume: volumes, - VolumesFrom: cc.HostConfig.VolumesFrom, - Workdir: cc.Config.WorkingDir, - Net: &netInfo, + User: cc.Config.User, + UserNS: string(cc.HostConfig.UsernsMode), + UTS: string(cc.HostConfig.UTSMode), + Mount: mounts, + Volume: volumes, + VolumesFrom: cc.HostConfig.VolumesFrom, + Workdir: cc.Config.WorkingDir, + Net: &netInfo, + } + if !rootless.IsRootless() { + var ulimits []string + if len(cc.HostConfig.Ulimits) > 0 { + for _, ul := range cc.HostConfig.Ulimits { + ulimits = append(ulimits, ul.String()) + } + cliOpts.Ulimit = ulimits + } } if len(cc.HostConfig.BlkioWeightDevice) > 0 { @@ -377,7 +407,11 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont cliOpts.MemoryReservation = strconv.Itoa(int(cc.HostConfig.MemoryReservation)) } - if cc.HostConfig.MemorySwap > 0 { + cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, nil, err + } + if cc.HostConfig.MemorySwap > 0 && (!rootless.IsRootless() || (rootless.IsRootless() && cgroupsv2)) { cliOpts.MemorySwap = strconv.Itoa(int(cc.HostConfig.MemorySwap)) } @@ -401,8 +435,10 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont cliOpts.Restart = policy } - if cc.HostConfig.MemorySwappiness != nil { + if cc.HostConfig.MemorySwappiness != nil && (!rootless.IsRootless() || rootless.IsRootless() && cgroupsv2 && cgroupsManager == "systemd") { cliOpts.MemorySwappiness = *cc.HostConfig.MemorySwappiness + } else { + cliOpts.MemorySwappiness = -1 } if cc.HostConfig.OomKillDisable != nil { cliOpts.OOMKillDisable = *cc.HostConfig.OomKillDisable @@ -416,7 +452,70 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*Cont } // specgen assumes the image name is arg[0] - cmd := []string{cc.Image} + cmd := []string{cc.Config.Image} cmd = append(cmd, cc.Config.Cmd...) return &cliOpts, cmd, nil } + +func ulimits() []string { + if !registry.IsRemote() { + return containerConfig.Ulimits() + } + return nil +} + +func cgroupConfig() string { + if !registry.IsRemote() { + return containerConfig.Cgroups() + } + return "" +} + +func devices() []string { + if !registry.IsRemote() { + return containerConfig.Devices() + } + return nil +} + +func env() []string { + if !registry.IsRemote() { + return containerConfig.Env() + } + return nil +} + +func initPath() string { + if !registry.IsRemote() { + return containerConfig.InitPath() + } + return "" +} + +func pidsLimit() int64 { + if !registry.IsRemote() { + return containerConfig.PidsLimit() + } + return -1 +} + +func policy() string { + if !registry.IsRemote() { + return containerConfig.Engine.PullPolicy + } + return "" +} + +func shmSize() string { + if !registry.IsRemote() { + return containerConfig.ShmSize() + } + return "" +} + +func volumes() []string { + if !registry.IsRemote() { + return containerConfig.Volumes() + } + return nil +} diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go index 09ee5aa0c..3a69f11b6 100644 --- a/cmd/podman/common/createparse.go +++ b/cmd/podman/common/createparse.go @@ -9,7 +9,7 @@ import ( // by validate must not need any state information on the flag (i.e. changed) func (c *ContainerCLIOpts) validate() error { var () - if c.Rm && c.Restart != "" && c.Restart != "no" { + if c.Rm && (c.Restart != "" && c.Restart != "no" && c.Restart != "on-failure") { return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`) } diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index 1b8297c36..898d65bd0 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -4,54 +4,85 @@ import ( "net" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/specgen" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) -func GetNetFlags() *pflag.FlagSet { - netFlags := pflag.FlagSet{} +func DefineNetFlags(cmd *cobra.Command) { + netFlags := cmd.Flags() + + addHostFlagName := "add-host" netFlags.StringSlice( - "add-host", []string{}, + addHostFlagName, []string{}, "Add a custom host-to-IP mapping (host:ip) (default [])", ) + _ = cmd.RegisterFlagCompletionFunc(addHostFlagName, completion.AutocompleteNone) + + dnsFlagName := "dns" netFlags.StringSlice( - "dns", containerConfig.DNSServers(), + dnsFlagName, containerConfig.DNSServers(), "Set custom DNS servers", ) + _ = cmd.RegisterFlagCompletionFunc(dnsFlagName, completion.AutocompleteNone) + + dnsOptFlagName := "dns-opt" netFlags.StringSlice( - "dns-opt", containerConfig.DNSOptions(), + dnsOptFlagName, containerConfig.DNSOptions(), "Set custom DNS options", ) + _ = cmd.RegisterFlagCompletionFunc(dnsOptFlagName, completion.AutocompleteNone) + + dnsSearchFlagName := "dns-search" netFlags.StringSlice( - "dns-search", containerConfig.DNSSearches(), + dnsSearchFlagName, containerConfig.DNSSearches(), "Set custom DNS search domains", ) + _ = cmd.RegisterFlagCompletionFunc(dnsSearchFlagName, completion.AutocompleteNone) + + ipFlagName := "ip" netFlags.String( - "ip", "", + ipFlagName, "", "Specify a static IPv4 address for the container", ) + _ = cmd.RegisterFlagCompletionFunc(ipFlagName, completion.AutocompleteNone) + + macAddressFlagName := "mac-address" netFlags.String( - "mac-address", "", + macAddressFlagName, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)", ) - netFlags.String( - "network", containerConfig.NetNS(), + _ = cmd.RegisterFlagCompletionFunc(macAddressFlagName, completion.AutocompleteNone) + + networkFlagName := "network" + netFlags.StringArray( + networkFlagName, []string{containerConfig.NetNS()}, "Connect a container to a network", ) + _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworks) + + networkAliasFlagName := "network-alias" + netFlags.StringSlice( + networkAliasFlagName, []string{}, + "Add network-scoped alias for the container", + ) + _ = cmd.RegisterFlagCompletionFunc(networkAliasFlagName, completion.AutocompleteNone) + + publishFlagName := "publish" netFlags.StringSliceP( - "publish", "p", []string{}, + publishFlagName, "p", []string{}, "Publish a container's port, or a range of ports, to the host (default [])", ) + _ = cmd.RegisterFlagCompletionFunc(publishFlagName, completion.AutocompleteNone) + netFlags.Bool( "no-hosts", false, "Do not create /etc/hosts within the container, instead use the version from the image", ) - return &netFlags } func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { @@ -158,28 +189,42 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { } opts.NoHosts, err = cmd.Flags().GetBool("no-hosts") + if err != nil { + return nil, err + } if cmd.Flags().Changed("network") { - network, err := cmd.Flags().GetString("network") + networks, err := cmd.Flags().GetStringArray("network") if err != nil { return nil, err } + for i, network := range networks { + parts := strings.SplitN(network, ":", 2) - parts := strings.SplitN(network, ":", 2) - - ns, cniNets, err := specgen.ParseNetworkNamespace(network) - if err != nil { - return nil, err - } + ns, cniNets, err := specgen.ParseNetworkNamespace(network) + if err != nil { + return nil, err + } + if i > 0 && (len(cniNets) == 0 || len(opts.CNINetworks) == 0) { + return nil, errors.Errorf("network conflict between type %s and %s", opts.Network.NSMode, ns.NSMode) + } - if len(parts) > 1 { - opts.NetworkOptions = make(map[string][]string) - opts.NetworkOptions[parts[0]] = strings.Split(parts[1], ",") - cniNets = nil + if len(parts) > 1 { + opts.NetworkOptions = make(map[string][]string) + opts.NetworkOptions[parts[0]] = strings.Split(parts[1], ",") + cniNets = nil + } + opts.Network = ns + opts.CNINetworks = append(opts.CNINetworks, cniNets...) } - opts.Network = ns - opts.CNINetworks = cniNets } + aliases, err := cmd.Flags().GetStringSlice("network-alias") + if err != nil { + return nil, err + } + if len(aliases) > 0 { + opts.Aliases = aliases + } return &opts, err } diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index f427830c6..0bb6e79e5 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -25,11 +25,8 @@ func getCPULimits(c *ContainerCLIOpts) *specs.LinuxCPU { cpu := &specs.LinuxCPU{} hasLimits := false - const cpuPeriod = 100000 - if c.CPUS > 0 { - quota := int64(c.CPUS * cpuPeriod) - period := uint64(cpuPeriod) + period, quota := util.CoresToPeriodAndQuota(c.CPUS) cpu.Period = &period cpu.Quota = "a @@ -399,6 +396,17 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.ShmSize = &shmSize } s.CNINetworks = c.Net.CNINetworks + + // Network aliases + if len(c.Net.Aliases) > 0 { + // build a map of aliases where key=cniName + aliases := make(map[string][]string, len(s.CNINetworks)) + for _, cniNetwork := range s.CNINetworks { + aliases[cniNetwork] = c.Net.Aliases + } + s.Aliases = aliases + } + s.HostAdd = c.Net.AddHosts s.UseImageResolvConf = c.Net.UseImageResolvConf s.DNSServers = c.Net.DNSServers @@ -533,13 +541,14 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // Only add read-only tmpfs mounts in case that we are read-only and the // read-only tmpfs flag has been set. - mounts, volumes, overlayVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) + mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) if err != nil { return err } s.Mounts = mounts s.Volumes = volumes s.OverlayVolumes = overlayVolumes + s.ImageVolumes = imageVolumes for _, dev := range c.Devices { s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) @@ -583,7 +592,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string case "max-size": logSize, err := units.FromHumanSize(split[1]) if err != nil { - return errors.Wrapf(err, "%s is not a valid option", o) + return err } s.LogConfiguration.Size = logSize default: @@ -653,7 +662,7 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start } intervalDuration, err := time.ParseDuration(interval) if err != nil { - return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", interval) + return nil, errors.Wrapf(err, "invalid healthcheck-interval") } hc.Interval = intervalDuration @@ -664,7 +673,7 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start hc.Retries = int(retries) timeoutDuration, err := time.ParseDuration(timeout) if err != nil { - return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", timeout) + return nil, errors.Wrapf(err, "invalid healthcheck-timeout") } if timeoutDuration < time.Duration(1) { return nil, errors.New("healthcheck-timeout must be at least 1 second") @@ -673,7 +682,7 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start startPeriodDuration, err := time.ParseDuration(startPeriod) if err != nil { - return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", startPeriod) + return nil, errors.Wrapf(err, "invalid healthcheck-start-period") } if startPeriodDuration < time.Duration(0) { return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index a971aa957..ef30e08d3 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -250,7 +250,7 @@ func parseAndValidateRange(portRange string) (uint16, uint16, error) { func parseAndValidatePort(port string) (uint16, error) { num, err := strconv.Atoi(port) if err != nil { - return 0, errors.Wrapf(err, "cannot parse %q as a port number", port) + return 0, errors.Wrapf(err, "invalid port number") } if num < 1 || num > 65535 { return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num) diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go index 71f897264..dfbb7b1b2 100644 --- a/cmd/podman/common/volumes.go +++ b/cmd/podman/common/volumes.go @@ -10,7 +10,6 @@ import ( "github.com/containers/podman/v2/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) const ( @@ -37,43 +36,43 @@ var ( // Does not handle image volumes, init, and --volumes-from flags. // Can also add tmpfs mounts from read-only tmpfs. // TODO: handle options parsing/processing via containers/storage/pkg/mount -func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { +func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) { // Get mounts from the --mounts flag. - unifiedMounts, unifiedVolumes, err := getMounts(mountFlag) + unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := getMounts(mountFlag) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Next --volumes flag. - volumeMounts, volumeVolumes, overlayVolumes, err := getVolumeMounts(volumeFlag) + volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Next --tmpfs flag. tmpfsMounts, err := getTmpfsMounts(tmpfsFlag) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Unify mounts from --mount, --volume, --tmpfs. // Start with --volume. for dest, mount := range volumeMounts { if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) + return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) } unifiedMounts[dest] = mount } for dest, volume := range volumeVolumes { if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) + return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) } unifiedVolumes[dest] = volume } // Now --tmpfs for dest, tmpfs := range tmpfsMounts { if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) + return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) } unifiedMounts[dest] = tmpfs } @@ -98,29 +97,34 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo } } - // Check for conflicts between named volumes, overlay volumes, and mounts - for dest := range unifiedMounts { - if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + // Check for conflicts between named volumes, overlay & image volumes, + // and mounts + allMounts := make(map[string]bool) + testAndSet := func(dest string) error { + if _, ok := allMounts[dest]; ok { + return errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) } - if _, ok := overlayVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + allMounts[dest] = true + return nil + } + for dest := range unifiedMounts { + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } } for dest := range unifiedVolumes { - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - } - if _, ok := overlayVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } } for dest := range overlayVolumes { - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } - if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } + for dest := range unifiedImageVolumes { + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } } @@ -130,7 +134,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo if mount.Type == TypeBind { absSrc, err := filepath.Abs(mount.Source) if err != nil { - return nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) + return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) } mount.Source = absSrc } @@ -144,8 +148,12 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo for _, volume := range overlayVolumes { finalOverlayVolume = append(finalOverlayVolume, volume) } + finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedImageVolumes)) + for _, volume := range unifiedImageVolumes { + finalImageVolumes = append(finalImageVolumes, volume) + } - return finalMounts, finalVolumes, finalOverlayVolume, nil + return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil } // findMountType parses the input and extracts the type of the mount type and @@ -174,59 +182,69 @@ func findMountType(input string) (mountType string, tokens []string, err error) // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... // podman run --mount type=tmpfs,target=/dev/shm ... // podman run --mount type=volume,source=test-volume, ... -func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { +func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) { finalMounts := make(map[string]spec.Mount) finalNamedVolumes := make(map[string]*specgen.NamedVolume) + finalImageVolumes := make(map[string]*specgen.ImageVolume) for _, mount := range mountFlag { // TODO: Docker defaults to "volume" if no mount type is specified. mountType, tokens, err := findMountType(mount) if err != nil { - return nil, nil, err + return nil, nil, nil, err } switch mountType { case TypeBind: mount, err := getBindMount(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) } finalMounts[mount.Destination] = mount case TypeTmpfs: mount, err := getTmpfsMount(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) } finalMounts[mount.Destination] = mount case TypeDevpts: mount, err := getDevptsMount(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) } finalMounts[mount.Destination] = mount + case "image": + volume, err := getImageVolume(tokens) + if err != nil { + return nil, nil, nil, err + } + if _, ok := finalImageVolumes[volume.Destination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Destination) + } + finalImageVolumes[volume.Destination] = volume case "volume": volume, err := getNamedVolume(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalNamedVolumes[volume.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) } finalNamedVolumes[volume.Dest] = volume default: - return nil, nil, errors.Errorf("invalid filesystem type %q", mountType) + return nil, nil, nil, errors.Errorf("invalid filesystem type %q", mountType) } } - return finalMounts, finalNamedVolumes, nil + return finalMounts, finalNamedVolumes, finalImageVolumes, nil } // Parse a single bind mount entry from the --mount flag. @@ -294,7 +312,7 @@ func getBindMount(args []string) (spec.Mount, error) { } setExec = true newMount.Options = append(newMount.Options, kv[0]) - case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": + case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z": newMount.Options = append(newMount.Options, kv[0]) case "bind-propagation": if len(kv) == 1 { @@ -305,8 +323,8 @@ func getBindMount(args []string) (spec.Mount, error) { if len(kv) == 1 { return newMount, errors.Wrapf(optionArgError, kv[0]) } - if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { - return newMount, err + if len(kv[1]) == 0 { + return newMount, errors.Wrapf(optionArgError, "host directory cannot be empty") } newMount.Source = kv[1] setSource = true @@ -531,103 +549,48 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) { return newVolume, nil } -func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.OverlayVolume, error) { - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*specgen.NamedVolume) - overlayVolumes := make(map[string]*specgen.OverlayVolume) - - volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") - - for _, vol := range volumeFlag { - var ( - options []string - src string - dest string - err error - ) - - splitVol := strings.Split(vol, ":") - if len(splitVol) > 3 { - return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol) - } - - src = splitVol[0] - if len(splitVol) == 1 { - // This is an anonymous named volume. Only thing given - // is destination. - // Name/source will be blank, and populated by libpod. - src = "" - dest = splitVol[0] - } else if len(splitVol) > 1 { - dest = splitVol[1] - } - if len(splitVol) > 2 { - if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { - return nil, nil, nil, err - } - } +// Parse the arguments into an image volume. An image volume is a volume based +// on a container image. The container image is first mounted on the host and +// is then bind-mounted into the container. An ImageVolume is always mounted +// read only. +func getImageVolume(args []string) (*specgen.ImageVolume, error) { + newVolume := new(specgen.ImageVolume) - // Do not check source dir for anonymous volumes - if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, nil, err + for _, val := range args { + kv := strings.SplitN(val, "=", 2) + switch kv[0] { + case "src", "source": + if len(kv) == 1 { + return nil, errors.Wrapf(optionArgError, kv[0]) } - } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, nil, err - } - - cleanDest := filepath.Clean(dest) - - if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { - // This is not a named volume - overlayFlag := false - for _, o := range options { - if o == "O" { - overlayFlag = true - if len(options) > 1 { - return nil, nil, nil, errors.New("can't use 'O' with other options") - } - } + newVolume.Source = kv[1] + case "target", "dst", "destination": + if len(kv) == 1 { + return nil, errors.Wrapf(optionArgError, kv[0]) } - if overlayFlag { - // This is a overlay volume - newOverlayVol := new(specgen.OverlayVolume) - newOverlayVol.Destination = cleanDest - newOverlayVol.Source = src - if _, ok := overlayVolumes[newOverlayVol.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, newOverlayVol.Destination) - } - overlayVolumes[newOverlayVol.Destination] = newOverlayVol - } else { - newMount := spec.Mount{ - Destination: cleanDest, - Type: string(TypeBind), - Source: src, - Options: options, - } - if _, ok := mounts[newMount.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) - } - mounts[newMount.Destination] = newMount + if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + return nil, err } - } else { - // This is a named volume - newNamedVol := new(specgen.NamedVolume) - newNamedVol.Name = src - newNamedVol.Dest = cleanDest - newNamedVol.Options = options - - if _, ok := volumes[newNamedVol.Dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) + newVolume.Destination = filepath.Clean(kv[1]) + case "rw", "readwrite": + switch kv[1] { + case "true": + newVolume.ReadWrite = true + case "false": + // Nothing to do. RO is default. + default: + return nil, errors.Wrapf(util.ErrBadMntOption, "invalid rw value %q", kv[1]) } - volumes[newNamedVol.Dest] = newNamedVol + default: + return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) } + } - logrus.Debugf("User mount %s:%s options %v", src, dest, options) + if len(newVolume.Source)*len(newVolume.Destination) == 0 { + return nil, errors.Errorf("must set source and destination for image volume") } - return mounts, volumes, overlayVolumes, nil + return newVolume, nil } // GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts diff --git a/cmd/podman/completion/completion.go b/cmd/podman/completion/completion.go new file mode 100644 index 000000000..ead8d1f05 --- /dev/null +++ b/cmd/podman/completion/completion.go @@ -0,0 +1,93 @@ +package completion + +import ( + "fmt" + "io" + "os" + "strings" + + commonComp "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +const ( + completionDescription = `Generate shell autocompletions. + Valid arguments are bash, zsh, and fish. + Please refer to the man page to see how you can load these completions.` +) + +var ( + file string + noDesc bool + shells = []string{"bash", "zsh", "fish"} + completionCmd = &cobra.Command{ + Use: fmt.Sprintf("completion [options] {%s}", strings.Join(shells, "|")), + Short: "Generate shell autocompletions", + Long: completionDescription, + ValidArgs: shells, + Args: cobra.ExactValidArgs(1), + RunE: completion, + Example: `podman completion bash + podman completion zsh -f _podman + podman completion fish --no-desc`, + //dont show this command to users + Hidden: true, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: completionCmd, + }) + flags := completionCmd.Flags() + fileFlagName := "file" + flags.StringVarP(&file, fileFlagName, "f", "", "Output the completion to file rather than stdout.") + _ = completionCmd.RegisterFlagCompletionFunc(fileFlagName, commonComp.AutocompleteDefault) + + flags.BoolVar(&noDesc, "no-desc", false, "Don't include descriptions in the completion output.") +} + +func completion(cmd *cobra.Command, args []string) error { + var w io.Writer + + if file != "" { + file, err := os.Create(file) + if err != nil { + return err + } + defer file.Close() + w = file + } else { + w = os.Stdout + } + + var err error + switch args[0] { + case "bash": + if noDesc { + err = cmd.Root().GenBashCompletion(w) + } else { + err = cmd.Root().GenBashCompletionWithDesc(w) + } + case "zsh": + if noDesc { + err = cmd.Root().GenZshCompletionNoDesc(w) + } else { + err = cmd.Root().GenZshCompletion(w) + } + case "fish": + err = cmd.Root().GenFishCompletion(w, !noDesc) + } + + if err != nil { + return err + } + + _, err = io.WriteString(w, fmt.Sprintf( + "\n# This file is generated with %q; see: podman-completion(1)\n", cmd.CommandPath(), + )) + return err +} diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index 0db7c22d0..f00c5378b 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.go @@ -3,33 +3,35 @@ package containers import ( "os" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( attachDescription = "The podman attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." attachCommand = &cobra.Command{ - Use: "attach [options] CONTAINER", - Short: "Attach to a running container", - Long: attachDescription, - RunE: attach, - Args: validate.IDOrLatestArgs, + Use: "attach [options] CONTAINER", + Short: "Attach to a running container", + Long: attachDescription, + RunE: attach, + Args: validate.IDOrLatestArgs, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman attach ctrID podman attach 1234 podman attach --no-stdin foobar`, } containerAttachCommand = &cobra.Command{ - Use: attachCommand.Use, - Short: attachCommand.Short, - Long: attachCommand.Long, - RunE: attachCommand.RunE, - Args: validate.IDOrLatestArgs, + Use: attachCommand.Use, + Short: attachCommand.Short, + Long: attachCommand.Long, + RunE: attachCommand.RunE, + Args: validate.IDOrLatestArgs, + ValidArgsFunction: attachCommand.ValidArgsFunction, Example: `podman container attach ctrID podman container attach 1234 podman container attach --no-stdin foobar`, @@ -40,8 +42,13 @@ var ( attachOpts entities.AttachOptions ) -func attachFlags(flags *pflag.FlagSet) { - flags.StringVar(&attachOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") +func attachFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + detachKeysFlagName := "detach-keys" + flags.StringVar(&attachOpts.DetachKeys, detachKeysFlagName, containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + _ = cmd.RegisterFlagCompletionFunc(detachKeysFlagName, common.AutocompleteDetachKeys) + flags.BoolVar(&attachOpts.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") flags.BoolVar(&attachOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") } @@ -51,7 +58,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: attachCommand, }) - attachFlags(attachCommand.Flags()) + attachFlags(attachCommand) validate.AddLatestFlag(attachCommand, &attachOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -59,7 +66,7 @@ func init() { Command: containerAttachCommand, Parent: containerCmd, }) - attachFlags(containerAttachCommand.Flags()) + attachFlags(containerAttachCommand) validate.AddLatestFlag(containerAttachCommand, &attachOpts.Latest) } diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index 2606f62c5..b6dc21348 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -27,6 +29,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman container checkpoint --keep ctrID podman container checkpoint --all podman container checkpoint --leave-running --latest`, @@ -48,7 +51,11 @@ func init() { flags.BoolVarP(&checkpointOptions.LeaveRunning, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") flags.BoolVar(&checkpointOptions.TCPEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") flags.BoolVarP(&checkpointOptions.All, "all", "a", false, "Checkpoint all running containers") - flags.StringVarP(&checkpointOptions.Export, "export", "e", "", "Export the checkpoint image to a tar.gz") + + exportFlagName := "export" + flags.StringVarP(&checkpointOptions.Export, exportFlagName, "e", "", "Export the checkpoint image to a tar.gz") + _ = checkpointCommand.RegisterFlagCompletionFunc(exportFlagName, completion.AutocompleteDefault) + flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting") validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest) } diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go index 54bc64a60..593aaad3d 100644 --- a/cmd/podman/containers/cleanup.go +++ b/cmd/podman/containers/cleanup.go @@ -3,6 +3,8 @@ package containers import ( "fmt" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -26,6 +28,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainersExited, Example: `podman container cleanup --latest podman container cleanup ctrID1 ctrID2 ctrID3 podman container cleanup --all`, @@ -44,7 +47,11 @@ func init() { }) flags := cleanupCommand.Flags() flags.BoolVarP(&cleanupOptions.All, "all", "a", false, "Cleans up all containers") - flags.StringVar(&cleanupOptions.Exec, "exec", "", "Clean up the given exec session instead of the container") + + execFlagName := "exec" + flags.StringVar(&cleanupOptions.Exec, execFlagName, "", "Clean up the given exec session instead of the container") + _ = cleanupCommand.RegisterFlagCompletionFunc(execFlagName, completion.AutocompleteNone) + flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") validate.AddLatestFlag(cleanupCommand, &cleanupOptions.Latest) diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index 1b33d221d..c5c7673b2 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -7,22 +7,24 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( commitDescription = `Create an image from a container's changes. Optionally tag the image created, set the author with the --author flag, set the commit message with the --message flag, and make changes to the instructions with the --change flag.` commitCommand = &cobra.Command{ - Use: "commit [options] CONTAINER [IMAGE]", - Short: "Create new image based on the changed container", - Long: commitDescription, - RunE: commit, - Args: cobra.RangeArgs(1, 2), + Use: "commit [options] CONTAINER [IMAGE]", + Short: "Create new image based on the changed container", + Long: commitDescription, + RunE: commit, + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: common.AutocompleteContainers, Example: `podman commit -q --message "committing container to image" reverent_golick image-committed podman commit -q --author "firstName lastName" reverent_golick image-committed podman commit -q --pause=false containerID image-committed @@ -30,19 +32,17 @@ var ( } containerCommitCommand = &cobra.Command{ - Args: commitCommand.Args, - Use: commitCommand.Use, - Short: commitCommand.Short, - Long: commitCommand.Long, - RunE: commitCommand.RunE, + Args: commitCommand.Args, + Use: commitCommand.Use, + Short: commitCommand.Short, + Long: commitCommand.Long, + RunE: commitCommand.RunE, + ValidArgsFunction: commitCommand.ValidArgsFunction, Example: `podman container commit -q --message "committing container to image" reverent_golick image-committed podman container commit -q --author "firstName lastName" reverent_golick image-committed podman container commit -q --pause=false containerID image-committed podman container commit containerID`, } - - // ChangeCmds is the list of valid Changes commands to passed to the Commit call - ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"} ) var ( @@ -52,12 +52,29 @@ var ( iidFile string ) -func commitFlags(flags *pflag.FlagSet) { - flags.StringArrayVarP(&commitOptions.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(ChangeCmds, " | ")) - flags.StringVarP(&commitOptions.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") - flags.StringVarP(&iidFile, "iidfile", "", "", "`file` to write the image ID to") - flags.StringVarP(&commitOptions.Message, "message", "m", "", "Set commit message for imported image") - flags.StringVarP(&commitOptions.Author, "author", "a", "", "Set the author for the image committed") +func commitFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + changeFlagName := "change" + flags.StringArrayVarP(&commitOptions.Changes, changeFlagName, "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(common.ChangeCmds, " | ")) + _ = cmd.RegisterFlagCompletionFunc(changeFlagName, common.AutocompleteChangeInstructions) + + formatFlagName := "format" + flags.StringVarP(&commitOptions.Format, formatFlagName, "f", "oci", "`Format` of the image manifest and metadata") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteImageFormat) + + iidFileFlagName := "iidfile" + flags.StringVarP(&iidFile, iidFileFlagName, "", "", "`file` to write the image ID to") + _ = cmd.RegisterFlagCompletionFunc(iidFileFlagName, completion.AutocompleteDefault) + + messageFlagName := "message" + flags.StringVarP(&commitOptions.Message, messageFlagName, "m", "", "Set commit message for imported image") + _ = cmd.RegisterFlagCompletionFunc(messageFlagName, completion.AutocompleteNone) + + authorFlagName := "author" + flags.StringVarP(&commitOptions.Author, authorFlagName, "a", "", "Set the author for the image committed") + _ = cmd.RegisterFlagCompletionFunc(authorFlagName, completion.AutocompleteNone) + flags.BoolVarP(&commitOptions.Pause, "pause", "p", false, "Pause container during commit") flags.BoolVarP(&commitOptions.Quiet, "quiet", "q", false, "Suppress output") flags.BoolVar(&commitOptions.IncludeVolumes, "include-volumes", false, "Include container volumes as image volumes") @@ -68,16 +85,14 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: commitCommand, }) - flags := commitCommand.Flags() - commitFlags(flags) + commitFlags(commitCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerCommitCommand, Parent: containerCmd, }) - containerCommitFlags := containerCommitCommand.Flags() - commitFlags(containerCommitFlags) + commitFlags(containerCommitCommand) } func commit(cmd *cobra.Command, args []string) error { @@ -95,7 +110,7 @@ func commit(cmd *cobra.Command, args []string) error { } if len(iidFile) > 0 { if err = ioutil.WriteFile(iidFile, []byte(response.Id), 0644); err != nil { - return errors.Wrapf(err, "failed to write image ID to file %q", iidFile) + return errors.Wrap(err, "failed to write image ID") } } fmt.Println(response.Id) diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index 5dfe43ded..a74ea89d2 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -1,13 +1,13 @@ package containers import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/cgroups" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/rootless" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -16,21 +16,23 @@ var ( You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or directory. ` cpCommand = &cobra.Command{ - Use: "cp [options] SRC_PATH DEST_PATH", - Short: "Copy files/folders between a container and the local filesystem", - Long: cpDescription, - Args: cobra.ExactArgs(2), - RunE: cp, - Example: "podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", + Use: "cp [options] SRC_PATH DEST_PATH", + Short: "Copy files/folders between a container and the local filesystem", + Long: cpDescription, + Args: cobra.ExactArgs(2), + RunE: cp, + ValidArgsFunction: common.AutocompleteCpCommand, + Example: "podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", } containerCpCommand = &cobra.Command{ - Use: cpCommand.Use, - Short: cpCommand.Short, - Long: cpCommand.Long, - Args: cpCommand.Args, - RunE: cpCommand.RunE, - Example: "podman container cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", + Use: cpCommand.Use, + Short: cpCommand.Short, + Long: cpCommand.Long, + Args: cpCommand.Args, + RunE: cpCommand.RunE, + ValidArgsFunction: cpCommand.ValidArgsFunction, + Example: "podman container cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", } ) @@ -38,7 +40,8 @@ var ( cpOpts entities.ContainerCpOptions ) -func cpFlags(flags *pflag.FlagSet) { +func cpFlags(cmd *cobra.Command) { + flags := cmd.Flags() flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.") flags.BoolVar(&cpOpts.Pause, "pause", copyPause(), "Pause the container while copying") } @@ -48,16 +51,14 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode}, Command: cpCommand, }) - flags := cpCommand.Flags() - cpFlags(flags) + cpFlags(cpCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: containerCpCommand, Parent: containerCmd, }) - containerCpFlags := containerCpCommand.Flags() - cpFlags(containerCpFlags) + cpFlags(containerCpCommand) } func cp(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index b7b2a364f..db920554e 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -19,7 +19,6 @@ import ( "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -27,22 +26,24 @@ var ( The container ID is then printed to stdout. You can then start it at any time with the podman start <container_id> command. The container will be created with the initial state 'created'.` createCommand = &cobra.Command{ - Use: "create [options] IMAGE [COMMAND [ARG...]]", - Short: "Create but do not start a container", - Long: createDescription, - RunE: create, - Args: cobra.MinimumNArgs(1), + Use: "create [options] IMAGE [COMMAND [ARG...]]", + Short: "Create but do not start a container", + Long: createDescription, + RunE: create, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: common.AutocompleteCreateRun, Example: `podman create alpine ls podman create --annotation HELLO=WORLD alpine ls podman create -t -i --name myctr alpine ls`, } containerCreateCommand = &cobra.Command{ - Args: cobra.MinimumNArgs(1), - Use: createCommand.Use, - Short: createCommand.Short, - Long: createCommand.Long, - RunE: createCommand.RunE, + Args: cobra.MinimumNArgs(1), + Use: createCommand.Use, + Short: createCommand.Short, + Long: createCommand.Long, + RunE: createCommand.RunE, + ValidArgsFunction: createCommand.ValidArgsFunction, Example: `podman container create alpine ls podman container create --annotation HELLO=WORLD alpine ls podman container create -t -i --name myctr alpine ls`, @@ -53,10 +54,13 @@ var ( cliVals common.ContainerCLIOpts ) -func createFlags(flags *pflag.FlagSet) { +func createFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.SetInterspersed(false) - flags.AddFlagSet(common.GetCreateFlags(&cliVals)) - flags.AddFlagSet(common.GetNetFlags()) + common.DefineCreateFlags(cmd, &cliVals) + common.DefineNetFlags(cmd) + flags.SetNormalizeFunc(utils.AliasFlags) _ = flags.MarkHidden("signature-policy") @@ -70,18 +74,14 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: createCommand, }) - // common.GetCreateFlags(createCommand) - flags := createCommand.Flags() - createFlags(flags) + createFlags(createCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerCreateCommand, Parent: containerCmd, }) - - containerCreateFlags := containerCreateCommand.Flags() - createFlags(containerCreateFlags) + createFlags(containerCreateCommand) } func create(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index caf7e9955..4533b7159 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -13,11 +13,12 @@ import ( var ( // podman container _diff_ diffCmd = &cobra.Command{ - Use: "diff [options] CONTAINER", - Args: validate.IDOrLatestArgs, - Short: "Inspect changes to the container's file systems", - Long: `Displays changes to the container filesystem's'. The container will be compared to its parent layer.`, - RunE: diff, + Use: "diff [options] CONTAINER", + Args: validate.IDOrLatestArgs, + Short: "Inspect changes to the container's file systems", + Long: `Displays changes to the container filesystem's'. The container will be compared to its parent layer.`, + RunE: diff, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman container diff myCtr podman container diff -l --format json myCtr`, } @@ -35,7 +36,11 @@ func init() { flags := diffCmd.Flags() flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkHidden("archive") - flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") + + formatFlagName := "format" + flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format") + _ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + validate.AddLatestFlag(diffCmd, &diffOpts.Latest) } diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 88851f619..306bae58e 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -5,6 +5,8 @@ import ( "fmt" "os" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -13,7 +15,6 @@ import ( "github.com/containers/podman/v2/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -25,6 +26,7 @@ var ( Long: execDescription, RunE: exec, DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman exec -it ctrID ls podman exec -it -w /tmp myCtr pwd podman exec --user root ctrID ls`, @@ -36,6 +38,7 @@ var ( Long: execCommand.Long, RunE: execCommand.RunE, DisableFlagsInUseLine: true, + ValidArgsFunction: execCommand.ValidArgsFunction, Example: `podman container exec -it ctrID ls podman container exec -it -w /tmp myCtr pwd podman container exec --user root ctrID ls`, @@ -48,18 +51,39 @@ var ( execDetach bool ) -func execFlags(flags *pflag.FlagSet) { +func execFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.SetInterspersed(false) flags.BoolVarP(&execDetach, "detach", "d", false, "Run the exec session in detached mode (backgrounded)") - flags.StringVar(&execOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") - flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables") - flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables") + + detachKeysFlagName := "detach-keys" + flags.StringVar(&execOpts.DetachKeys, detachKeysFlagName, containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + _ = cmd.RegisterFlagCompletionFunc(detachKeysFlagName, common.AutocompleteDetachKeys) + + envFlagName := "env" + flags.StringArrayVarP(&envInput, envFlagName, "e", []string{}, "Set environment variables") + _ = cmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) + + envFileFlagName := "env-file" + flags.StringSliceVar(&envFile, envFileFlagName, []string{}, "Read in a file of environment variables") + _ = cmd.RegisterFlagCompletionFunc(envFileFlagName, completion.AutocompleteDefault) + flags.BoolVarP(&execOpts.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") flags.BoolVar(&execOpts.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") flags.BoolVarP(&execOpts.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") - flags.StringVarP(&execOpts.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") - flags.UintVar(&execOpts.PreserveFDs, "preserve-fds", 0, "Pass N additional file descriptors to the container") - flags.StringVarP(&execOpts.WorkDir, "workdir", "w", "", "Working directory inside the container") + + userFlagName := "user" + flags.StringVarP(&execOpts.User, userFlagName, "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") + _ = cmd.RegisterFlagCompletionFunc(userFlagName, common.AutocompleteUserFlag) + + preserveFdsFlagName := "preserve-fds" + flags.UintVar(&execOpts.PreserveFDs, preserveFdsFlagName, 0, "Pass N additional file descriptors to the container") + _ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone) + + workdirFlagName := "workdir" + flags.StringVarP(&execOpts.WorkDir, workdirFlagName, "w", "", "Working directory inside the container") + _ = cmd.RegisterFlagCompletionFunc(workdirFlagName, completion.AutocompleteDefault) if registry.IsRemote() { _ = flags.MarkHidden("preserve-fds") @@ -71,7 +95,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: execCommand, }) - execFlags(execCommand.Flags()) + execFlags(execCommand) validate.AddLatestFlag(execCommand, &execOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -79,7 +103,7 @@ func init() { Command: containerExecCommand, Parent: containerCmd, }) - execFlags(containerExecCommand.Flags()) + execFlags(containerExecCommand) validate.AddLatestFlag(containerExecCommand, &execOpts.Latest) } diff --git a/cmd/podman/containers/exists.go b/cmd/podman/containers/exists.go index 70b8af159..607a03023 100644 --- a/cmd/podman/containers/exists.go +++ b/cmd/podman/containers/exists.go @@ -3,6 +3,7 @@ package containers import ( "context" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -20,6 +21,7 @@ var ( RunE: exists, Args: cobra.ExactArgs(1), DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteContainers, } ) diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index f5e02d134..fe2da0f3a 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -4,12 +4,13 @@ import ( "context" "os" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -18,21 +19,23 @@ var ( " and saves it on the local machine." exportCommand = &cobra.Command{ - Use: "export [options] CONTAINER", - Short: "Export container's filesystem contents as a tar archive", - Long: exportDescription, - RunE: export, - Args: cobra.ExactArgs(1), + Use: "export [options] CONTAINER", + Short: "Export container's filesystem contents as a tar archive", + Long: exportDescription, + RunE: export, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteContainers, Example: `podman export ctrID > myCtr.tar podman export --output="myCtr.tar" ctrID`, } containerExportCommand = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: exportCommand.Use, - Short: exportCommand.Short, - Long: exportCommand.Long, - RunE: exportCommand.RunE, + Args: cobra.ExactArgs(1), + Use: exportCommand.Use, + Short: exportCommand.Short, + Long: exportCommand.Long, + RunE: exportCommand.RunE, + ValidArgsFunction: exportCommand.ValidArgsFunction, Example: `podman container export ctrID > myCtr.tar podman container export --output="myCtr.tar" ctrID`, } @@ -42,8 +45,12 @@ var ( exportOpts entities.ContainerExportOptions ) -func exportFlags(flags *pflag.FlagSet) { - flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") +func exportFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + outputFlagName := "output" + flags.StringVarP(&exportOpts.Output, outputFlagName, "o", "", "Write to a specified file (default: stdout, which must be redirected)") + _ = cmd.RegisterFlagCompletionFunc(outputFlagName, completion.AutocompleteDefault) } func init() { @@ -51,17 +58,14 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: exportCommand, }) - flags := exportCommand.Flags() - exportFlags(flags) + exportFlags(exportCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerExportCommand, Parent: containerCmd, }) - - containerExportFlags := containerExportCommand.Flags() - exportFlags(containerExportFlags) + exportFlags(containerExportCommand) } func export(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/init.go b/cmd/podman/containers/init.go index 983c0e4e8..99a32bc24 100644 --- a/cmd/podman/containers/init.go +++ b/cmd/podman/containers/init.go @@ -3,6 +3,7 @@ package containers import ( "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -22,17 +23,19 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainersCreated, Example: `podman init --latest podman init 3c45ef19d893 podman init test1`, } containerInitCommand = &cobra.Command{ - Use: initCommand.Use, - Short: initCommand.Short, - Long: initCommand.Long, - RunE: initCommand.RunE, - Args: initCommand.Args, + Use: initCommand.Use, + Short: initCommand.Short, + Long: initCommand.Long, + RunE: initCommand.RunE, + Args: initCommand.Args, + ValidArgsFunction: initCommand.ValidArgsFunction, Example: `podman container init --latest podman container init 3c45ef19d893 podman container init test1`, diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go index b4e1feccb..e438bca95 100644 --- a/cmd/podman/containers/inspect.go +++ b/cmd/podman/containers/inspect.go @@ -1,6 +1,7 @@ package containers import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/inspect" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" @@ -11,10 +12,11 @@ import ( var ( // podman container _inspect_ inspectCmd = &cobra.Command{ - Use: "inspect [options] CONTAINER [CONTAINER...]", - Short: "Display the configuration of a container", - Long: `Displays the low-level information on a container identified by name or ID.`, - RunE: inspectExec, + Use: "inspect [options] CONTAINER [CONTAINER...]", + Short: "Display the configuration of a container", + Long: `Displays the low-level information on a container identified by name or ID.`, + RunE: inspectExec, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman container inspect myCtr podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, } @@ -30,7 +32,11 @@ func init() { inspectOpts = new(entities.InspectOptions) flags := inspectCmd.Flags() flags.BoolVarP(&inspectOpts.Size, "size", "s", false, "Display total file size") - flags.StringVarP(&inspectOpts.Format, "format", "f", "json", "Format the output to a Go template or json") + + formatFlagName := "format" + flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "json", "Format the output to a Go template or json") + _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + validate.AddLatestFlag(inspectCmd, &inspectOpts.Latest) } diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 1bb071b6d..4640229a9 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -5,13 +5,13 @@ import ( "errors" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/signal" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -24,6 +24,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman kill mywebserver podman kill 860a4b23 podman kill --signal TERM ctrID`, @@ -33,10 +34,11 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, - Use: killCommand.Use, - Short: killCommand.Short, - Long: killCommand.Long, - RunE: killCommand.RunE, + Use: killCommand.Use, + Short: killCommand.Short, + Long: killCommand.Long, + RunE: killCommand.RunE, + ValidArgsFunction: killCommand.ValidArgsFunction, Example: `podman container kill mywebserver podman container kill 860a4b23 podman container kill --signal TERM ctrID`, @@ -47,9 +49,14 @@ var ( killOptions = entities.KillOptions{} ) -func killFlags(flags *pflag.FlagSet) { +func killFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&killOptions.All, "all", "a", false, "Signal all running containers") - flags.StringVarP(&killOptions.Signal, "signal", "s", "KILL", "Signal to send to the container") + + signalFlagName := "signal" + flags.StringVarP(&killOptions.Signal, signalFlagName, "s", "KILL", "Signal to send to the container") + _ = cmd.RegisterFlagCompletionFunc(signalFlagName, common.AutocompleteStopSignal) } func init() { @@ -57,7 +64,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: killCommand, }) - killFlags(killCommand.Flags()) + killFlags(killCommand) validate.AddLatestFlag(killCommand, &killOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -65,7 +72,7 @@ func init() { Command: containerKillCommand, Parent: containerCmd, }) - killFlags(containerKillCommand.Flags()) + killFlags(containerKillCommand) validate.AddLatestFlag(containerKillCommand, &killOptions.Latest) } diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go index 78a15559f..834413c2c 100644 --- a/cmd/podman/containers/list.go +++ b/cmd/podman/containers/list.go @@ -1,6 +1,7 @@ package containers import ( + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -10,12 +11,13 @@ import ( var ( // podman container _list_ listCmd = &cobra.Command{ - Use: "list [options]", - Aliases: []string{"ls"}, - Args: validate.NoArgs, - Short: "List containers", - Long: "Prints out information about the containers", - RunE: ps, + Use: "list [options]", + Aliases: []string{"ls"}, + Args: validate.NoArgs, + Short: "List containers", + Long: "Prints out information about the containers", + RunE: ps, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman container list -a podman container list -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" podman container list --size --sort names`, @@ -28,6 +30,6 @@ func init() { Command: listCmd, Parent: containerCmd, }) - listFlagSet(listCmd.Flags()) + listFlagSet(listCmd) validate.AddLatestFlag(listCmd, &listOpts.Latest) } diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 8ad2d7e16..1fa4ac11f 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -3,13 +3,14 @@ package containers import ( "os" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // logsOptionsWrapper wraps entities.LogsOptions and prevents leaking @@ -43,7 +44,8 @@ var ( } return nil }, - RunE: logs, + RunE: logs, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman logs ctrID podman logs --names ctrID1 ctrID2 podman logs --tail 2 mywebserver @@ -52,11 +54,12 @@ var ( } containerLogsCommand = &cobra.Command{ - Use: logsCommand.Use, - Short: logsCommand.Short, - Long: logsCommand.Long, - Args: logsCommand.Args, - RunE: logsCommand.RunE, + Use: logsCommand.Use, + Short: logsCommand.Short, + Long: logsCommand.Long, + Args: logsCommand.Args, + RunE: logsCommand.RunE, + ValidArgsFunction: logsCommand.ValidArgsFunction, Example: `podman container logs ctrID podman container logs --names ctrID1 ctrID2 podman container logs --tail 2 mywebserver @@ -71,7 +74,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: logsCommand, }) - logsFlags(logsCommand.Flags()) + logsFlags(logsCommand) validate.AddLatestFlag(logsCommand, &logsOptions.Latest) // container logs @@ -80,15 +83,24 @@ func init() { Command: containerLogsCommand, Parent: containerCmd, }) - logsFlags(containerLogsCommand.Flags()) + logsFlags(containerLogsCommand) validate.AddLatestFlag(containerLogsCommand, &logsOptions.Latest) } -func logsFlags(flags *pflag.FlagSet) { +func logsFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs") flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output. The default is false") - flags.StringVar(&logsOptions.SinceRaw, "since", "", "Show logs since TIMESTAMP") - flags.Int64Var(&logsOptions.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") + + sinceFlagName := "since" + flags.StringVar(&logsOptions.SinceRaw, sinceFlagName, "", "Show logs since TIMESTAMP") + _ = cmd.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone) + + tailFlagName := "tail" + flags.Int64Var(&logsOptions.Tail, tailFlagName, -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") + _ = cmd.RegisterFlagCompletionFunc(tailFlagName, completion.AutocompleteNone) + flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log") flags.SetInterspersed(false) diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index 335367e18..fb2101d64 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -7,13 +7,13 @@ import ( "text/template" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -36,15 +36,17 @@ var ( registry.UnshareNSRequired: "", registry.ParentNSRequired: "", }, + ValidArgsFunction: common.AutocompleteContainers, } containerMountCommmand = &cobra.Command{ - Use: mountCommand.Use, - Short: mountCommand.Short, - Long: mountCommand.Long, - RunE: mountCommand.RunE, - Args: mountCommand.Args, - Annotations: mountCommand.Annotations, + Use: mountCommand.Use, + Short: mountCommand.Short, + Long: mountCommand.Long, + RunE: mountCommand.RunE, + Args: mountCommand.Args, + Annotations: mountCommand.Annotations, + ValidArgsFunction: mountCommand.ValidArgsFunction, } ) @@ -52,9 +54,15 @@ var ( mountOpts entities.ContainerMountOptions ) -func mountFlags(flags *pflag.FlagSet) { +func mountFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers") - flags.StringVar(&mountOpts.Format, "format", "", "Print the mounted containers in specified format (json)") + + formatFlagName := "format" + flags.StringVar(&mountOpts.Format, formatFlagName, "", "Print the mounted containers in specified format (json)") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output") } @@ -63,7 +71,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode}, Command: mountCommand, }) - mountFlags(mountCommand.Flags()) + mountFlags(mountCommand) validate.AddLatestFlag(mountCommand, &mountOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -71,7 +79,7 @@ func init() { Command: containerMountCommmand, Parent: containerCmd, }) - mountFlags(containerMountCommmand.Flags()) + mountFlags(containerMountCommmand) validate.AddLatestFlag(containerMountCommmand, &mountOpts.Latest) } diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go index 89a76ab25..18b37adfd 100644 --- a/cmd/podman/containers/pause.go +++ b/cmd/podman/containers/pause.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/cgroups" @@ -17,20 +18,22 @@ import ( var ( pauseDescription = `Pauses one or more running containers. The container name or ID can be used.` pauseCommand = &cobra.Command{ - Use: "pause [options] CONTAINER [CONTAINER...]", - Short: "Pause all the processes in one or more containers", - Long: pauseDescription, - RunE: pause, + Use: "pause [options] CONTAINER [CONTAINER...]", + Short: "Pause all the processes in one or more containers", + Long: pauseDescription, + RunE: pause, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman pause mywebserver podman pause 860a4b23 podman pause -a`, } containerPauseCommand = &cobra.Command{ - Use: pauseCommand.Use, - Short: pauseCommand.Short, - Long: pauseCommand.Long, - RunE: pauseCommand.RunE, + Use: pauseCommand.Use, + Short: pauseCommand.Short, + Long: pauseCommand.Long, + RunE: pauseCommand.RunE, + ValidArgsFunction: pauseCommand.ValidArgsFunction, Example: `podman container pause mywebserver podman container pause 860a4b23 podman container pause -a`, diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index a895f24b1..ac31e158e 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -25,6 +26,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman port --all podman port ctrID 80/tcp podman port --latest 80`, @@ -38,6 +40,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, + ValidArgsFunction: portCommand.ValidArgsFunction, Example: `podman container port --all podman container port --latest 80`, } diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go index bfdace086..9ac529b1c 100644 --- a/cmd/podman/containers/prune.go +++ b/cmd/podman/containers/prune.go @@ -8,6 +8,7 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -21,12 +22,13 @@ var ( Removes all non running containers`) pruneCommand = &cobra.Command{ - Use: "prune [options]", - Short: "Remove all non running containers", - Long: pruneDescription, - RunE: prune, - Example: `podman container prune`, - Args: validate.NoArgs, + Use: "prune [options]", + Short: "Remove all non running containers", + Long: pruneDescription, + RunE: prune, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman container prune`, + Args: validate.NoArgs, } force bool filter = []string{} @@ -40,7 +42,9 @@ func init() { }) flags := pruneCommand.Flags() flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") - flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + filterFlagName := "filter" + flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) } func prune(cmd *cobra.Command, args []string) error { @@ -53,7 +57,7 @@ func prune(cmd *cobra.Command, args []string) error { fmt.Print("Are you sure you want to continue? [y/N] ") answer, err := reader.ReadString('\n') if err != nil { - return errors.Wrapf(err, "error reading input") + return err } if strings.ToLower(answer)[0] != 'y' { return nil diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 90f4db19c..6f84cf9b8 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -11,7 +11,10 @@ import ( "time" tm "github.com/buger/goterm" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -20,29 +23,38 @@ import ( "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( psDescription = "Prints out information about the containers" psCommand = &cobra.Command{ - Use: "ps [options]", - Args: validate.NoArgs, - Short: "List containers", - Long: psDescription, - RunE: ps, + Use: "ps [options]", + Short: "List containers", + Long: psDescription, + RunE: ps, + Args: validate.NoArgs, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman ps -a podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" podman ps --size --sort names`, } + + psContainerCommand = &cobra.Command{ + Use: psCommand.Use, + Short: psCommand.Short, + Long: psCommand.Long, + RunE: psCommand.RunE, + Args: psCommand.Args, + ValidArgsFunction: psCommand.ValidArgsFunction, + Example: strings.ReplaceAll(psCommand.Example, "podman ps", "podman container ps"), + } ) var ( listOpts = entities.ContainerListOptions{ Filters: make(map[string][]string), } - filters []string - noTrunc bool - defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" + filters []string + noTrunc bool ) func init() { @@ -50,26 +62,52 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: psCommand, }) - listFlagSet(psCommand.Flags()) + listFlagSet(psCommand) validate.AddLatestFlag(psCommand, &listOpts.Latest) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: psContainerCommand, + Parent: containerCmd, + }) + listFlagSet(psContainerCommand) + validate.AddLatestFlag(psContainerCommand, &listOpts.Latest) } -func listFlagSet(flags *pflag.FlagSet) { +func listFlagSet(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&listOpts.All, "all", "a", false, "Show all the containers, default is only running containers") - flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given") flags.BoolVar(&listOpts.Storage, "external", false, "Show containers in storage not controlled by Podman") - flags.StringVar(&listOpts.Format, "format", "", "Pretty-print containers to JSON or using a Go template") - flags.IntVarP(&listOpts.Last, "last", "n", -1, "Print the n last created containers (all states)") + + filterFlagName := "filter" + flags.StringSliceVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given") + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters) + + formatFlagName := "format" + flags.StringVar(&listOpts.Format, formatFlagName, "", "Pretty-print containers to JSON or using a Go template") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + + lastFlagName := "last" + flags.IntVarP(&listOpts.Last, lastFlagName, "n", -1, "Print the n last created containers (all states)") + _ = cmd.RegisterFlagCompletionFunc(lastFlagName, completion.AutocompleteNone) + flags.BoolVar(&listOpts.Namespace, "ns", false, "Display namespace information") flags.BoolVar(&noTrunc, "no-trunc", false, "Display the extended information") flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes") flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime") - flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") + + watchFlagName := "watch" + flags.UintVarP(&listOpts.Watch, watchFlagName, "w", 0, "Watch the ps output on an interval in seconds") + _ = cmd.RegisterFlagCompletionFunc(watchFlagName, completion.AutocompleteNone) sort := validate.Value(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") - flags.Var(sort, "sort", "Sort output by: "+sort.Choices()) + sortFlagName := "sort" + flags.Var(sort, sortFlagName, "Sort output by: "+sort.Choices()) + _ = cmd.RegisterFlagCompletionFunc(sortFlagName, common.AutocompletePsSort) + flags.SetNormalizeFunc(utils.AliasFlags) } func checkFlags(c *cobra.Command) error { @@ -77,24 +115,12 @@ func checkFlags(c *cobra.Command) error { if listOpts.Last >= 0 && listOpts.Latest { return errors.Errorf("last and latest are mutually exclusive") } - // Filter on status forces all - for _, filter := range filters { - splitFilter := strings.SplitN(filter, "=", 2) - if strings.ToLower(splitFilter[0]) == "status" { - listOpts.All = true - break - } - } // Quiet conflicts with size and namespace and is overridden by a Go // template. if listOpts.Quiet { if listOpts.Size || listOpts.Namespace { return errors.Errorf("quiet conflicts with size and namespace") } - if c.Flag("format").Changed && !report.IsJSON(listOpts.Format) { - // Quiet is overridden by Go template output. - listOpts.Quiet = false - } } // Size and namespace conflict with each other if listOpts.Size && listOpts.Namespace { @@ -155,7 +181,7 @@ func getResponses() ([]entities.ListContainer, error) { return responses, nil } -func ps(cmd *cobra.Command, args []string) error { +func ps(cmd *cobra.Command, _ []string) error { if err := checkFlags(cmd); err != nil { return err } @@ -180,24 +206,22 @@ func ps(cmd *cobra.Command, args []string) error { switch { case report.IsJSON(listOpts.Format): return jsonOut(listContainers) - case listOpts.Quiet: + case listOpts.Quiet && !cmd.Flags().Changed("format"): return quietOut(listContainers) } - // Output table Watch > 0 will refresh screen responses := make([]psReporter, 0, len(listContainers)) for _, r := range listContainers { responses = append(responses, psReporter{r}) } - var headers, format string + hdrs, format := createPsOut() if cmd.Flags().Changed("format") { - headers = "" format = report.NormalizeFormat(listOpts.Format) - } else { - headers, format = createPsOut() + format = parse.EnforceRange(format) } - format = headers + "{{range . }}" + format + "{{end}}" + ns := strings.NewReplacer(".Namespaces.", ".") + format = ns.Replace(format) tmpl, err := template.New("listContainers").Parse(format) if err != nil { @@ -206,13 +230,19 @@ func ps(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if listOpts.Watch > 0 { - for { - var responses []psReporter - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() + headers := func() error { return nil } + if !(listOpts.Quiet || cmd.Flags().Changed("format")) { + headers = func() error { + return tmpl.Execute(w, hdrs) + } + } + switch { + // Output table Watch > 0 will refresh screen + case listOpts.Watch > 0: + // responses will grow to the largest number of processes reported on, but will not thrash the gc + var responses []psReporter + for ; ; responses = responses[:0] { if ctnrs, err := getResponses(); err != nil { return err } else { @@ -221,18 +251,27 @@ func ps(cmd *cobra.Command, args []string) error { } } + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + + if err := headers(); err != nil { + return err + } if err := tmpl.Execute(w, responses); err != nil { return err } if err := w.Flush(); err != nil { + // we usually do not care about Flush() failures but here do not loop if Flush() has failed return err } + time.Sleep(time.Duration(listOpts.Watch) * time.Second) - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() } - } else if listOpts.Watch < 1 { + default: + if err := headers(); err != nil { + return err + } if err := tmpl.Execute(w, responses); err != nil { return err } @@ -241,30 +280,36 @@ func ps(cmd *cobra.Command, args []string) error { } // cannot use report.Headers() as it doesn't support structures as fields -func createPsOut() (string, string) { +func createPsOut() ([]map[string]string, string) { + hdrs := report.Headers(psReporter{}, map[string]string{ + "Cgroup": "cgroupns", + "CreatedHuman": "created", + "ID": "container id", + "IPC": "ipc", + "MNT": "mnt", + "NET": "net", + "PIDNS": "pidns", + "Pod": "pod id", + "PodName": "podname", // undo camelcase space break + "UTS": "uts", + "User": "userns", + }) + var row string if listOpts.Namespace { - headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDNS\tUSERNS\tUTS\n" - row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" - return headers, row - } - headers := defaultHeaders - row += "{{.ID}}" - row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" + row = "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}" + } else { + row = "{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" - if listOpts.Pod { - headers += "\tPOD ID\tPODNAME" - row += "\t{{.Pod}}\t{{.PodName}}" - } + if listOpts.Pod { + row += "\t{{.Pod}}\t{{.PodName}}" + } - if listOpts.Size { - headers += "\tSIZE" - row += "\t{{.Size}}" + if listOpts.Size { + row += "\t{{.Size}}" + } } - - headers = report.NormalizeFormat(headers) - row = report.NormalizeFormat(row) - return headers, row + return hdrs, "{{range .}}" + row + "\n{{end}}" } type psReporter struct { @@ -367,6 +412,41 @@ func (l psReporter) CreatedHuman() string { return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago" } +// Cgroup exposes .Namespaces.Cgroup +func (l psReporter) Cgroup() string { + return l.Namespaces.Cgroup +} + +// IPC exposes .Namespaces.IPC +func (l psReporter) IPC() string { + return l.Namespaces.IPC +} + +// MNT exposes .Namespaces.MNT +func (l psReporter) MNT() string { + return l.Namespaces.MNT +} + +// NET exposes .Namespaces.NET +func (l psReporter) NET() string { + return l.Namespaces.NET +} + +// PIDNS exposes .Namespaces.PIDNS +func (l psReporter) PIDNS() string { + return l.Namespaces.PIDNS +} + +// User exposes .Namespaces.User +func (l psReporter) User() string { + return l.Namespaces.User +} + +// UTS exposes .Namespaces.UTS +func (l psReporter) UTS() string { + return l.Namespaces.UTS +} + // portsToString converts the ports used to a string of the from "port1, port2" // and also groups a continuous list of ports into a readable format. func portsToString(ports []ocicni.PortMapping) string { diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index 1cc28c20d..a51355194 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -11,7 +13,6 @@ import ( "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -27,16 +28,18 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman restart ctrID podman restart --latest podman restart ctrID1 ctrID2`, } containerRestartCommand = &cobra.Command{ - Use: restartCommand.Use, - Short: restartCommand.Short, - Long: restartCommand.Long, - RunE: restartCommand.RunE, + Use: restartCommand.Use, + Short: restartCommand.Short, + Long: restartCommand.Long, + RunE: restartCommand.RunE, + ValidArgsFunction: restartCommand.ValidArgsFunction, Example: `podman container restart ctrID podman container restart --latest podman container restart ctrID1 ctrID2`, @@ -48,10 +51,15 @@ var ( restartTimeout uint ) -func restartFlags(flags *pflag.FlagSet) { +func restartFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers") flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used") - flags.UintVarP(&restartTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") + + timeFlagName := "time" + flags.UintVarP(&restartTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") + _ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) flags.SetNormalizeFunc(utils.AliasFlags) } @@ -61,7 +69,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: restartCommand, }) - restartFlags(restartCommand.Flags()) + restartFlags(restartCommand) validate.AddLatestFlag(restartCommand, &restartOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -69,7 +77,7 @@ func init() { Command: containerRestartCommand, Parent: containerCmd, }) - restartFlags(containerRestartCommand.Flags()) + restartFlags(containerRestartCommand) validate.AddLatestFlag(containerRestartCommand, &restartOptions.Latest) } diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index 314bf7564..6a1d2b319 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -27,6 +29,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman container restore ctrID podman container restore --latest podman container restore --all`, @@ -47,8 +50,15 @@ func init() { flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers") flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections") - flags.StringVarP(&restoreOptions.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)") - flags.StringVarP(&restoreOptions.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") + + importFlagName := "import" + flags.StringVarP(&restoreOptions.Import, importFlagName, "i", "", "Restore from exported checkpoint archive (tar.gz)") + _ = restoreCommand.RegisterFlagCompletionFunc(importFlagName, completion.AutocompleteDefault) + + nameFlagName := "name" + flags.StringVarP(&restoreOptions.Name, nameFlagName, "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") + _ = restoreCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) + flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint") flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip") flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address") diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index ccdd2ef05..ee9dc4c78 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -13,7 +15,6 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -28,6 +29,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman rm imageID podman rm mywebserver myflaskserver 860a4b23 podman rm --force --all @@ -42,6 +44,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, + ValidArgsFunction: rmCommand.ValidArgsFunction, Example: `podman container rm imageID podman container rm mywebserver myflaskserver 860a4b23 podman container rm --force --all @@ -53,12 +56,17 @@ var ( rmOptions = entities.RmOptions{} ) -func rmFlags(flags *pflag.FlagSet) { +func rmFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") - flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") + + cidfileFlagName := "cidfile" + flags.StringArrayVarP(&rmOptions.CIDFiles, cidfileFlagName, "", nil, "Read the container ID from the file") + _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) if !registry.IsRemote() { // This option is deprecated, but needs to still exists for backwards compatibility @@ -72,7 +80,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: rmCommand, }) - rmFlags(rmCommand.Flags()) + rmFlags(rmCommand) validate.AddLatestFlag(rmCommand, &rmOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -80,7 +88,7 @@ func init() { Command: containerRmCommand, Parent: containerCmd, }) - rmFlags(containerRmCommand.Flags()) + rmFlags(containerRmCommand) validate.AddLatestFlag(containerRmCommand, &rmOptions.Latest) } diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 6cadbc7ec..6ff1b929d 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" @@ -18,28 +19,29 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( runDescription = "Runs a command in a new container from the given image" runCommand = &cobra.Command{ - Args: cobra.MinimumNArgs(1), - Use: "run [options] IMAGE [COMMAND [ARG...]]", - Short: "Run a command in a new container", - Long: runDescription, - RunE: run, + Args: cobra.MinimumNArgs(1), + Use: "run [options] IMAGE [COMMAND [ARG...]]", + Short: "Run a command in a new container", + Long: runDescription, + RunE: run, + ValidArgsFunction: common.AutocompleteCreateRun, Example: `podman run imageID ls -alF /etc podman run --network=host imageID dnf -y install java podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, } containerRunCommand = &cobra.Command{ - Args: cobra.MinimumNArgs(1), - Use: runCommand.Use, - Short: runCommand.Short, - Long: runCommand.Long, - RunE: runCommand.RunE, + Args: cobra.MinimumNArgs(1), + Use: runCommand.Use, + Short: runCommand.Short, + Long: runCommand.Long, + RunE: runCommand.RunE, + ValidArgsFunction: runCommand.ValidArgsFunction, Example: `podman container run imageID ls -alF /etc podman container run --network=host imageID dnf -y install java podman container run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, @@ -55,16 +57,26 @@ var ( runRmi bool ) -func runFlags(flags *pflag.FlagSet) { +func runFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.SetInterspersed(false) - flags.AddFlagSet(common.GetCreateFlags(&cliVals)) - flags.AddFlagSet(common.GetNetFlags()) + common.DefineCreateFlags(cmd, &cliVals) + common.DefineNetFlags(cmd) + flags.SetNormalizeFunc(utils.AliasFlags) flags.BoolVar(&runOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") flags.BoolVar(&runRmi, "rmi", false, "Remove container image unless used by other containers") + + preserveFdsFlagName := "preserve-fds" flags.UintVar(&runOpts.PreserveFDs, "preserve-fds", 0, "Pass a number of additional file descriptors into the container") + _ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone) + flags.BoolVarP(&runOpts.Detach, "detach", "d", false, "Run container in background and print container ID") - flags.StringVar(&runOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + + detachKeysFlagName := "detach-keys" + flags.StringVar(&runOpts.DetachKeys, detachKeysFlagName, containerConfig.DetachKeys(), "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + _ = cmd.RegisterFlagCompletionFunc(detachKeysFlagName, common.AutocompleteDetachKeys) _ = flags.MarkHidden("signature-policy") if registry.IsRemote() { @@ -77,8 +89,8 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: runCommand, }) - flags := runCommand.Flags() - runFlags(flags) + + runFlags(runCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, @@ -86,8 +98,7 @@ func init() { Parent: containerCmd, }) - containerRunFlags := containerRunCommand.Flags() - runFlags(containerRunFlags) + runFlags(containerRunCommand) } func run(cmd *cobra.Command, args []string) error { @@ -108,7 +119,7 @@ func run(cmd *cobra.Command, args []string) error { if af := cliVals.Authfile; len(af) > 0 { if _, err := os.Stat(af); err != nil { - return errors.Wrapf(err, "error checking authfile path %s", af) + return err } } @@ -194,7 +205,7 @@ func run(cmd *cobra.Command, args []string) error { if runRmi { _, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), []string{imageName}, entities.ImageRemoveOptions{}) if len(rmErrors) > 0 { - logrus.Errorf("%s", errors.Wrapf(errorhandling.JoinErrors(rmErrors), "failed removing image")) + logrus.Errorf("%s", errorhandling.JoinErrors(rmErrors)) } } return nil diff --git a/cmd/podman/containers/runlabel.go b/cmd/podman/containers/runlabel.go index b49af36ab..2f6d2eb05 100644 --- a/cmd/podman/containers/runlabel.go +++ b/cmd/podman/containers/runlabel.go @@ -5,10 +5,11 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -24,11 +25,12 @@ var ( runlabelOptions = runlabelOptionsWrapper{} runlabelDescription = "Executes a command as described by a container image label." runlabelCommand = &cobra.Command{ - Use: "runlabel [options] LABEL IMAGE [ARG...]", - Short: "Execute the command described by an image label", - Long: runlabelDescription, - RunE: runlabel, - Args: cobra.MinimumNArgs(2), + Use: "runlabel [options] LABEL IMAGE [ARG...]", + Short: "Execute the command described by an image label", + Long: runlabelDescription, + RunE: runlabel, + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: common.AutocompleteImages, Example: `podman container runlabel run imageID podman container runlabel install imageID arg1 arg2 podman container runlabel --display run myImage`, @@ -43,11 +45,25 @@ func init() { }) flags := runlabelCommand.Flags() - flags.StringVar(&runlabelOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&runlabelOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.StringVar(&runlabelOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + + authfileflagName := "authfile" + flags.StringVar(&runlabelOptions.Authfile, authfileflagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = runlabelCommand.RegisterFlagCompletionFunc(authfileflagName, completion.AutocompleteDefault) + + certDirFlagName := "cert-dir" + flags.StringVar(&runlabelOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") + _ = runlabelCommand.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + + credsFlagName := "creds" + flags.StringVar(&runlabelOptions.Credentials, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = runlabelCommand.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + flags.BoolVar(&runlabelOptions.Display, "display", false, "Preview the command that the label would run") - flags.StringVarP(&runlabelOptions.Name, "name", "n", "", "Assign a name to the container") + + nameFlagName := "name" + flags.StringVarP(&runlabelOptions.Name, nameFlagName, "n", "", "Assign a name to the container") + _ = runlabelCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) + flags.StringVar(&runlabelOptions.Optional1, "opt1", "", "Optional parameter to pass for install") flags.StringVar(&runlabelOptions.Optional2, "opt2", "", "Optional parameter to pass for install") flags.StringVar(&runlabelOptions.Optional3, "opt3", "", "Optional parameter to pass for install") @@ -75,7 +91,7 @@ func runlabel(cmd *cobra.Command, args []string) error { } if runlabelOptions.Authfile != "" { if _, err := os.Stat(runlabelOptions.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", runlabelOptions.Authfile) + return err } } return registry.ContainerEngine().ContainerRunlabel(context.Background(), args[0], args[1], args[2:], runlabelOptions.ContainerRunlabelOptions) diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 21f31d360..7e57bb576 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -11,26 +12,29 @@ import ( "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( startDescription = `Starts one or more containers. The container name or ID can be used.` startCommand = &cobra.Command{ - Use: "start [options] CONTAINER [CONTAINER...]", - Short: "Start one or more containers", - Long: startDescription, - RunE: start, + Use: "start [options] CONTAINER [CONTAINER...]", + Short: "Start one or more containers", + Long: startDescription, + RunE: start, + Args: validateStart, + ValidArgsFunction: common.AutocompleteContainersStartable, Example: `podman start --latest podman start 860a4b231279 5421ab43b45 podman start --interactive --attach imageID`, } containerStartCommand = &cobra.Command{ - Use: startCommand.Use, - Short: startCommand.Short, - Long: startCommand.Long, - RunE: startCommand.RunE, + Use: startCommand.Use, + 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 podman container start --interactive --attach imageID`, @@ -41,9 +45,15 @@ var ( startOptions entities.ContainerStartOptions ) -func startFlags(flags *pflag.FlagSet) { +func startFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&startOptions.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") - flags.StringVar(&startOptions.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + + detachKeysFlagName := "detach-keys" + flags.StringVar(&startOptions.DetachKeys, detachKeysFlagName, containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + _ = cmd.RegisterFlagCompletionFunc(detachKeysFlagName, common.AutocompleteDetachKeys) + flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") @@ -56,7 +66,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: startCommand, }) - startFlags(startCommand.Flags()) + startFlags(startCommand) validate.AddLatestFlag(startCommand, &startOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -64,13 +74,11 @@ func init() { Command: containerStartCommand, Parent: containerCmd, }) - startFlags(containerStartCommand.Flags()) + startFlags(containerStartCommand) 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") } @@ -80,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 diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 85e7a1e82..ca5c0fdb8 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -8,6 +8,8 @@ import ( tm "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -19,28 +21,29 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." statsCommand = &cobra.Command{ - Use: "stats [options] [CONTAINER...]", - Short: "Display a live stream of container resource usage statistics", - Long: statsDescription, - RunE: stats, - Args: checkStatOptions, + Use: "stats [options] [CONTAINER...]", + Short: "Display a live stream of container resource usage statistics", + Long: statsDescription, + RunE: stats, + Args: checkStatOptions, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman stats --all --no-stream podman stats ctrID podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, } containerStatsCommand = &cobra.Command{ - Use: statsCommand.Use, - Short: statsCommand.Short, - Long: statsCommand.Long, - RunE: statsCommand.RunE, - Args: checkStatOptions, + Use: statsCommand.Use, + Short: statsCommand.Short, + Long: statsCommand.Long, + RunE: statsCommand.RunE, + Args: checkStatOptions, + ValidArgsFunction: statsCommand.ValidArgsFunction, Example: `podman container stats --all --no-stream podman container stats ctrID podman container stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, @@ -58,13 +61,18 @@ type statsOptionsCLI struct { } var ( - statsOptions statsOptionsCLI - defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" + statsOptions statsOptionsCLI ) -func statFlags(flags *pflag.FlagSet) { +func statFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") - flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + + formatFlagName := "format" + flags.StringVar(&statsOptions.Format, formatFlagName, "", "Pretty-print container statistics to JSON or using a Go template") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals") flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") } @@ -74,7 +82,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: statsCommand, }) - statFlags(statsCommand.Flags()) + statFlags(statsCommand) validate.AddLatestFlag(statsCommand, &statsOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -82,7 +90,7 @@ func init() { Command: containerStatsCommand, Parent: containerCmd, }) - statFlags(containerStatsCommand.Flags()) + statFlags(containerStatsCommand) validate.AddLatestFlag(containerStatsCommand, &statsOptions.Latest) } @@ -159,19 +167,19 @@ func outputStats(reports []define.ContainerStats) error { if report.IsJSON(statsOptions.Format) { return outputJSON(stats) } - format := defaultStatsRow - + format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" if len(statsOptions.Format) > 0 { format = report.NormalizeFormat(statsOptions.Format) - } else if len(statsOptions.Format) < 1 { - format = defaultStatsRow } - format = "{{range . }}" + format + "{{end}}" + format = parse.EnforceRange(format) + tmpl, err := template.New("stats").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + if len(statsOptions.Format) < 1 { if err := tmpl.Execute(w, headers); err != nil { return err @@ -180,9 +188,6 @@ func outputStats(reports []define.ContainerStats) error { if err := tmpl.Execute(w, stats); err != nil { return err } - if err := w.Flush(); err != nil { - return err - } return nil } diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 7c8c1b50e..3a4211357 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -4,12 +4,13 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -24,6 +25,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, + ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman stop ctrID podman stop --latest podman stop --time 2 mywebserver 6e534f14da9d`, @@ -37,6 +39,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, + ValidArgsFunction: stopCommand.ValidArgsFunction, Example: `podman container stop ctrID podman container stop --latest podman container stop --time 2 mywebserver 6e534f14da9d`, @@ -48,11 +51,19 @@ var ( stopTimeout uint ) -func stopFlags(flags *pflag.FlagSet) { +func stopFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running containers") flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") - flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") - flags.UintVarP(&stopTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") + + cidfileFlagName := "cidfile" + flags.StringArrayVarP(&stopOptions.CIDFiles, cidfileFlagName, "", nil, "Read the container ID from the file") + _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) + + timeFlagName := "time" + flags.UintVarP(&stopTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") + _ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) if registry.IsRemote() { _ = flags.MarkHidden("cidfile") @@ -66,7 +77,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: stopCommand, }) - stopFlags(stopCommand.Flags()) + stopFlags(stopCommand) validate.AddLatestFlag(stopCommand, &stopOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -74,8 +85,7 @@ func init() { Command: containerStopCommand, Parent: containerCmd, }) - - stopFlags(containerStopCommand.Flags()) + stopFlags(containerStopCommand) validate.AddLatestFlag(containerStopCommand, &stopOptions.Latest) } diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index 361d30516..e691f527a 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -7,6 +7,7 @@ import ( "strings" "text/tabwriter" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -17,20 +18,19 @@ import ( ) var ( - topDescription = `Similar to system "top" command. - - Specify format descriptors to alter the output. - - Running "podman top -l pid pcpu seccomp" will print the process ID, the CPU percentage and the seccomp mode of each process of the latest container.` + topDescription = `Display the running processes of a container. + The top command extends the ps(1) compatible AIX descriptors with container-specific ones as shown below. In the presence of ps(1) specific flags (e.g, -eo), Podman will execute ps(1) inside the container. +` topOptions = entities.TopOptions{} topCommand = &cobra.Command{ - Use: "top [options] CONTAINER [FORMAT-DESCRIPTORS|ARGS...]", - Short: "Display the running processes of a container", - Long: topDescription, - RunE: top, - Args: cobra.ArbitraryArgs, + Use: "top [options] CONTAINER [FORMAT-DESCRIPTORS|ARGS...]", + Short: "Display the running processes of a container", + Long: topDescription, + RunE: top, + Args: cobra.ArbitraryArgs, + ValidArgsFunction: common.AutocompleteTopCmd, Example: `podman top ctrID podman top --latest podman top ctrID pid seccomp args %C @@ -38,10 +38,11 @@ podman top ctrID -eo user,pid,comm`, } containerTopCommand = &cobra.Command{ - Use: topCommand.Use, - Short: topCommand.Short, - Long: topCommand.Long, - RunE: topCommand.RunE, + Use: topCommand.Use, + Short: topCommand.Short, + Long: topCommand.Long, + RunE: topCommand.RunE, + ValidArgsFunction: topCommand.ValidArgsFunction, Example: `podman container top ctrID podman container top --latest podman container top ctrID pid seccomp args %C diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index c3159cfed..22e0768e3 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -3,6 +3,7 @@ package containers import ( "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -27,6 +28,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman unmount ctrID podman unmount ctrID1 ctrID2 ctrID3 podman unmount --all`, @@ -41,6 +43,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman container unmount ctrID podman container unmount ctrID1 ctrID2 ctrID3 podman container unmount --all`, diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go index 8927fc426..0e8b59192 100644 --- a/cmd/podman/containers/unpause.go +++ b/cmd/podman/containers/unpause.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/cgroups" @@ -17,20 +18,22 @@ import ( var ( unpauseDescription = `Unpauses one or more previously paused containers. The container name or ID can be used.` unpauseCommand = &cobra.Command{ - Use: "unpause [options] CONTAINER [CONTAINER...]", - Short: "Unpause the processes in one or more containers", - Long: unpauseDescription, - RunE: unpause, + Use: "unpause [options] CONTAINER [CONTAINER...]", + Short: "Unpause the processes in one or more containers", + Long: unpauseDescription, + RunE: unpause, + ValidArgsFunction: common.AutocompleteContainersPaused, Example: `podman unpause ctrID podman unpause --all`, } unPauseOptions = entities.PauseUnPauseOptions{} containerUnpauseCommand = &cobra.Command{ - Use: unpauseCommand.Use, - Short: unpauseCommand.Short, - Long: unpauseCommand.Long, - RunE: unpauseCommand.RunE, + Use: unpauseCommand.Use, + Short: unpauseCommand.Short, + Long: unpauseCommand.Long, + RunE: unpauseCommand.RunE, + ValidArgsFunction: unpauseCommand.ValidArgsFunction, Example: `podman container unpause ctrID podman container unpause --all`, } diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index b4986143b..2bbfbccc9 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -12,26 +14,27 @@ import ( "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( waitDescription = `Block until one or more containers stop and then print their exit codes. ` waitCommand = &cobra.Command{ - Use: "wait [options] CONTAINER [CONTAINER...]", - Short: "Block on one or more containers", - Long: waitDescription, - RunE: wait, + Use: "wait [options] CONTAINER [CONTAINER...]", + Short: "Block on one or more containers", + Long: waitDescription, + RunE: wait, + ValidArgsFunction: common.AutocompleteContainers, Example: `podman wait --interval 5s ctrID podman wait ctrID1 ctrID2`, } containerWaitCommand = &cobra.Command{ - Use: waitCommand.Use, - Short: waitCommand.Short, - Long: waitCommand.Long, - RunE: waitCommand.RunE, + Use: waitCommand.Use, + Short: waitCommand.Short, + Long: waitCommand.Long, + RunE: waitCommand.RunE, + ValidArgsFunction: waitCommand.ValidArgsFunction, Example: `podman container wait --interval 5s ctrID podman container wait ctrID1 ctrID2`, } @@ -43,9 +46,17 @@ var ( waitInterval string ) -func waitFlags(flags *pflag.FlagSet) { - flags.StringVarP(&waitInterval, "interval", "i", "250ns", "Time Interval to wait before polling for completion") - flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on") +func waitFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + intervalFlagName := "interval" + flags.StringVarP(&waitInterval, intervalFlagName, "i", "250ns", "Time Interval to wait before polling for completion") + _ = cmd.RegisterFlagCompletionFunc(intervalFlagName, completion.AutocompleteNone) + + conditionFlagName := "condition" + flags.StringVar(&waitCondition, conditionFlagName, "stopped", "Condition to wait on") + _ = cmd.RegisterFlagCompletionFunc(conditionFlagName, common.AutocompleteWaitCondition) + } func init() { @@ -53,7 +64,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: waitCommand, }) - waitFlags(waitCommand.Flags()) + waitFlags(waitCommand) validate.AddLatestFlag(waitCommand, &waitOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -61,9 +72,8 @@ func init() { Command: containerWaitCommand, Parent: containerCmd, }) - waitFlags(containerWaitCommand.Flags()) + waitFlags(containerWaitCommand) validate.AddLatestFlag(containerWaitCommand, &waitOptions.Latest) - } func wait(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index bad31a4a2..5e6abe243 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/containers" "github.com/containers/podman/v2/cmd/podman/images" "github.com/containers/podman/v2/cmd/podman/registry" @@ -17,11 +18,12 @@ var ( // Command: podman _diff_ Object_ID diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.` diffCmd = &cobra.Command{ - Use: "diff [options] {CONTAINER_ID | IMAGE_ID}", - Args: validate.IDOrLatestArgs, - Short: "Display the changes to the object's file system", - Long: diffDescription, - RunE: diff, + Use: "diff [options] {CONTAINER_ID | IMAGE_ID}", + Args: validate.IDOrLatestArgs, + Short: "Display the changes to the object's file system", + Long: diffDescription, + RunE: diff, + ValidArgsFunction: common.AutocompleteContainersAndImages, Example: `podman diff imageID podman diff ctrID podman diff --format json redis:alpine`, @@ -38,7 +40,11 @@ func init() { flags := diffCmd.Flags() flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkHidden("archive") - flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") + + formatFlagName := "format" + flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format") + _ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + validate.AddLatestFlag(diffCmd, &diffOpts.Latest) } diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go index 4935fc60c..e47bd35b5 100644 --- a/cmd/podman/generate/kube.go +++ b/cmd/podman/generate/kube.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "os" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -20,11 +22,12 @@ var ( Whether the input is for a container or pod, Podman will always generate the specification as a pod.` kubeCmd = &cobra.Command{ - Use: "kube [options] CONTAINER | POD", - Short: "Generate Kubernetes YAML from a container or pod.", - Long: kubeDescription, - RunE: kube, - Args: cobra.ExactArgs(1), + Use: "kube [options] CONTAINER | POD", + Short: "Generate Kubernetes YAML from a container or pod.", + Long: kubeDescription, + RunE: kube, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteContainersAndPods, Example: `podman generate kube ctrID podman generate kube podID podman generate kube --service podID`, @@ -39,7 +42,11 @@ func init() { }) flags := kubeCmd.Flags() flags.BoolVarP(&kubeOptions.Service, "service", "s", false, "Generate YAML for a Kubernetes service object") - flags.StringVarP(&kubeFile, "filename", "f", "", "Write output to the specified path") + + filenameFlagName := "filename" + flags.StringVarP(&kubeFile, filenameFlagName, "f", "", "Write output to the specified path") + _ = kubeCmd.RegisterFlagCompletionFunc(filenameFlagName, completion.AutocompleteDefault) + flags.SetNormalizeFunc(utils.AliasFlags) } @@ -55,7 +62,7 @@ func kube(cmd *cobra.Command, args []string) error { } if cmd.Flags().Changed("filename") { if _, err := os.Stat(kubeFile); err == nil { - return errors.Errorf("cannot write to %q", kubeFile) + return errors.Errorf("cannot write to %q; file exists", kubeFile) } if err := ioutil.WriteFile(kubeFile, content, 0644); err != nil { return errors.Wrapf(err, "cannot write to %q", kubeFile) diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index 8e937fa90..e9cf76aae 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -6,7 +6,9 @@ import ( "os" "path/filepath" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -24,11 +26,12 @@ var ( The generated units can later be controlled via systemctl(1).` systemdCmd = &cobra.Command{ - Use: "systemd [options] CTR|POD", - Short: "Generate systemd units.", - Long: systemdDescription, - RunE: systemd, - Args: cobra.ExactArgs(1), + Use: "systemd [options] CTR|POD", + Short: "Generate systemd units.", + Long: systemdDescription, + RunE: systemd, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteContainersAndPods, Example: `podman generate systemd CTR podman generate systemd --new --time 10 CTR podman generate systemd --files --name POD`, @@ -44,13 +47,32 @@ func init() { flags := systemdCmd.Flags() flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs") flags.BoolVarP(&files, "files", "f", false, "Generate .service files instead of printing to stdout") - flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override") - flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy") + + timeFlagName := "time" + flags.UintVarP(&systemdTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Stop timeout override") + _ = systemdCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one") - flags.StringVar(&systemdOptions.ContainerPrefix, "container-prefix", "container", "Systemd unit name prefix for containers") - flags.StringVar(&systemdOptions.PodPrefix, "pod-prefix", "pod", "Systemd unit name prefix for pods") - flags.StringVar(&systemdOptions.Separator, "separator", "-", "Systemd unit name separator between name/id and prefix") - flags.StringVar(&format, "format", "", "Print the created units in specified format (json)") + + containerPrefixFlagName := "container-prefix" + flags.StringVar(&systemdOptions.ContainerPrefix, containerPrefixFlagName, "container", "Systemd unit name prefix for containers") + _ = systemdCmd.RegisterFlagCompletionFunc(containerPrefixFlagName, completion.AutocompleteNone) + + podPrefixFlagName := "pod-prefix" + flags.StringVar(&systemdOptions.PodPrefix, podPrefixFlagName, "pod", "Systemd unit name prefix for pods") + _ = systemdCmd.RegisterFlagCompletionFunc(podPrefixFlagName, completion.AutocompleteNone) + + separatorFlagName := "separator" + flags.StringVar(&systemdOptions.Separator, separatorFlagName, "-", "Systemd unit name separator between name/id and prefix") + _ = systemdCmd.RegisterFlagCompletionFunc(separatorFlagName, completion.AutocompleteNone) + + restartPolicyFlagName := "restart-policy" + flags.StringVar(&systemdOptions.RestartPolicy, restartPolicyFlagName, "on-failure", "Systemd restart-policy") + _ = systemdCmd.RegisterFlagCompletionFunc(restartPolicyFlagName, common.AutocompleteSystemdRestartOptions) + + formatFlagName := "format" + flags.StringVar(&format, formatFlagName, "", "Print the created units in specified format (json)") + _ = systemdCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.SetNormalizeFunc(utils.AliasFlags) } diff --git a/cmd/podman/healthcheck/run.go b/cmd/podman/healthcheck/run.go index 6d7f0b548..b4331d04e 100644 --- a/cmd/podman/healthcheck/run.go +++ b/cmd/podman/healthcheck/run.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -18,6 +19,7 @@ var ( Example: `podman healthcheck run mywebapp`, RunE: run, Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteContainersRunning, DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 18c31313b..739e1c265 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -9,16 +9,15 @@ import ( "github.com/containers/buildah/imagebuildah" buildahCLI "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" - "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal @@ -40,22 +39,24 @@ var ( // Command: podman _diff_ Object_ID buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory." buildCmd = &cobra.Command{ - Use: "build [options] [CONTEXT]", - Short: "Build an image using instructions from Containerfiles", - Long: buildDescription, - Args: cobra.MaximumNArgs(1), - RunE: build, + Use: "build [options] [CONTEXT]", + Short: "Build an image using instructions from Containerfiles", + Long: buildDescription, + Args: cobra.MaximumNArgs(1), + RunE: build, + ValidArgsFunction: completion.AutocompleteDefault, Example: `podman build . podman build --creds=username:password -t imageName -f Containerfile.simple . podman build --layers --force-rm --tag imageName .`, } imageBuildCmd = &cobra.Command{ - Args: buildCmd.Args, - Use: buildCmd.Use, - Short: buildCmd.Short, - Long: buildCmd.Long, - RunE: buildCmd.RunE, + Args: buildCmd.Args, + Use: buildCmd.Use, + Short: buildCmd.Short, + Long: buildCmd.Long, + RunE: buildCmd.RunE, + ValidArgsFunction: buildCmd.ValidArgsFunction, Example: `podman image build . podman image build --creds=username:password -t imageName -f Containerfile.simple . podman image build --layers --force-rm --tag imageName .`, @@ -79,22 +80,25 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: buildCmd, }) - buildFlags(buildCmd.Flags()) + buildFlags(buildCmd) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: imageBuildCmd, Parent: imageCmd, }) - buildFlags(imageBuildCmd.Flags()) + buildFlags(imageBuildCmd) } -func buildFlags(flags *pflag.FlagSet) { +func buildFlags(cmd *cobra.Command) { + flags := cmd.Flags() + // Podman flags flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer") // Bud flags budFlags := buildahCLI.GetBudFlags(&buildOpts.BudResults) + // --pull flag flag := budFlags.Lookup("pull") if err := flag.Value.Set("true"); err != nil { @@ -102,6 +106,9 @@ func buildFlags(flags *pflag.FlagSet) { } flag.DefValue = "true" flags.AddFlagSet(&budFlags) + // Add the completion functions + budCompletions := buildahCLI.GetBudFlagsCompletions() + completion.CompleteCommandFlags(cmd, budCompletions) // Layer flags layerFlags := buildahCLI.GetLayerFlags(&buildOpts.LayerResults) @@ -127,7 +134,11 @@ func buildFlags(flags *pflag.FlagSet) { os.Exit(1) } flags.AddFlagSet(&fromAndBudFlags) + // Add the completion functions + fromAndBudFlagsCompletions := buildahCLI.GetFromAndBudFlagsCompletions() + completion.CompleteCommandFlags(cmd, fromAndBudFlagsCompletions) _ = flags.MarkHidden("signature-policy") + flags.SetNormalizeFunc(buildahCLI.AliasFlags) } // build executes the build command. @@ -240,14 +251,18 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil } } - pullPolicy := imagebuildah.PullNever - if flags.Pull { - pullPolicy = imagebuildah.PullIfMissing + pullPolicy := imagebuildah.PullIfMissing + if c.Flags().Changed("pull") && flags.Pull { + pullPolicy = imagebuildah.PullAlways } if flags.PullAlways { pullPolicy = imagebuildah.PullAlways } + if flags.PullNever { + pullPolicy = imagebuildah.PullIfMissing + } + args := make(map[string]string) if c.Flag("build-arg").Changed { for _, arg := range flags.BuildArg { @@ -310,22 +325,11 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil } } - nsValues, err := getNsValues(flags) + nsValues, networkPolicy, err := parse.NamespaceOptions(c) if err != nil { return nil, err } - networkPolicy := buildah.NetworkDefault - for _, ns := range nsValues { - if ns.Name == "none" { - networkPolicy = buildah.NetworkDisabled - break - } else if !filepath.IsAbs(ns.Path) { - networkPolicy = buildah.NetworkEnabled - break - } - } - // `buildah bud --layers=false` acts like `docker build --squash` does. // That is all of the new layers created during the build process are // condensed into one, any layers present prior to this build are retained @@ -349,18 +353,18 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil isolation, err := parse.IsolationOption(flags.Isolation) if err != nil { - return nil, errors.Wrapf(err, "error parsing ID mapping options") + return nil, err } usernsOption, idmappingOptions, err := parse.IDMappingOptions(c, isolation) if err != nil { - return nil, errors.Wrapf(err, "error parsing ID mapping options") + return nil, err } nsValues = append(nsValues, usernsOption...) systemContext, err := parse.SystemContextFromOptions(c) if err != nil { - return nil, errors.Wrapf(err, "error building system context") + return nil, err } format := "" @@ -446,28 +450,3 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil return &entities.BuildOptions{BuildOptions: opts}, nil } - -func getNsValues(flags *buildFlagsWrapper) ([]buildah.NamespaceOption, error) { - var ret []buildah.NamespaceOption - if flags.Network != "" { - switch { - case flags.Network == "host": - ret = append(ret, buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - Host: true, - }) - case flags.Network == "container": - ret = append(ret, buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - }) - case flags.Network[0] == '/': - ret = append(ret, buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - Path: flags.Network, - }) - default: - return nil, errors.Errorf("unsupported configuration network=%s", flags.Network) - } - } - return ret, nil -} diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index b7722e5e5..71793c707 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -13,11 +13,12 @@ import ( var ( // podman container _inspect_ diffCmd = &cobra.Command{ - Use: "diff [options] IMAGE", - Args: cobra.ExactArgs(1), - Short: "Inspect changes to the image's file systems", - Long: `Displays changes to the image's filesystem. The image will be compared to its parent layer.`, - RunE: diff, + Use: "diff [options] IMAGE", + Args: cobra.ExactArgs(1), + Short: "Inspect changes to the image's file systems", + Long: `Displays changes to the image's filesystem. The image will be compared to its parent layer.`, + RunE: diff, + ValidArgsFunction: common.AutocompleteImages, Example: `podman image diff myImage podman image diff --format json redis:alpine`, } @@ -37,7 +38,10 @@ func diffFlags(flags *pflag.FlagSet) { diffOpts = &entities.DiffOptions{} flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") - flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") + + formatFlagName := "format" + flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format") + _ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func diff(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/exists.go b/cmd/podman/images/exists.go index 31bdc791e..cb1ca0295 100644 --- a/cmd/podman/images/exists.go +++ b/cmd/podman/images/exists.go @@ -1,6 +1,7 @@ package images import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -8,11 +9,12 @@ import ( var ( existsCmd = &cobra.Command{ - Use: "exists IMAGE", - Short: "Check if an image exists in local storage", - Long: `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.`, - Args: cobra.ExactArgs(1), - RunE: exists, + Use: "exists IMAGE", + Short: "Check if an image exists in local storage", + Long: `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.`, + Args: cobra.ExactArgs(1), + RunE: exists, + ValidArgsFunction: common.AutocompleteImages, Example: `podman image exists ID podman image exists IMAGE && podman pull IMAGE`, DisableFlagsInUseLine: true, diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index 3075218d1..964c7a975 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -11,12 +11,13 @@ import ( "unicode" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -26,21 +27,23 @@ var ( // podman _history_ historyCmd = &cobra.Command{ - Use: "history [options] IMAGE", - Short: "Show history of a specified image", - Long: long, - Args: cobra.ExactArgs(1), - RunE: history, - Example: "podman history quay.io/fedora/fedora", + Use: "history [options] IMAGE", + Short: "Show history of a specified image", + Long: long, + Args: cobra.ExactArgs(1), + RunE: history, + ValidArgsFunction: common.AutocompleteImages, + Example: "podman history quay.io/fedora/fedora", } imageHistoryCmd = &cobra.Command{ - Args: historyCmd.Args, - Use: historyCmd.Use, - Short: historyCmd.Short, - Long: historyCmd.Long, - RunE: historyCmd.RunE, - Example: `podman image history quay.io/fedora/fedora`, + Args: historyCmd.Args, + Use: historyCmd.Use, + Short: historyCmd.Short, + Long: historyCmd.Long, + ValidArgsFunction: historyCmd.ValidArgsFunction, + RunE: historyCmd.RunE, + Example: `podman image history quay.io/fedora/fedora`, } opts = struct { @@ -56,18 +59,23 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: historyCmd, }) - historyFlags(historyCmd.Flags()) + historyFlags(historyCmd) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: imageHistoryCmd, Parent: imageCmd, }) - historyFlags(imageHistoryCmd.Flags()) + historyFlags(imageHistoryCmd) } -func historyFlags(flags *pflag.FlagSet) { - flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") +func historyFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + formatFlagName := "format" + flags.StringVar(&opts.format, formatFlagName, "", "Change the output to JSON or a Go template") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") flags.BoolVar(&opts.noTrunc, "notruncate", false, "Do not truncate the output") @@ -119,7 +127,7 @@ func history(cmd *cobra.Command, args []string) error { case opts.quiet: row = "{{.ID}}\n" } - format := "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) tmpl, err := template.New("report").Parse(format) if err != nil { diff --git a/cmd/podman/images/images.go b/cmd/podman/images/images.go index 14ea01047..499bfd2ad 100644 --- a/cmd/podman/images/images.go +++ b/cmd/podman/images/images.go @@ -16,6 +16,7 @@ var ( Short: listCmd.Short, Long: listCmd.Long, RunE: listCmd.RunE, + ValidArgsFunction: listCmd.ValidArgsFunction, Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), DisableFlagsInUseLine: true, } @@ -27,5 +28,5 @@ func init() { Command: imagesCmd, }) - imageListFlagSet(imagesCmd.Flags()) + imageListFlagSet(imagesCmd) } diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index e3545da69..f38ab3b19 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -3,14 +3,16 @@ package images import ( "context" "fmt" + "strings" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -19,21 +21,23 @@ var ( Note remote tar balls can be specified, via web address. Optionally tag the image. You can specify the instructions using the --change option.` importCommand = &cobra.Command{ - Use: "import [options] PATH [REFERENCE]", - Short: "Import a tarball to create a filesystem image", - Long: importDescription, - RunE: importCon, + Use: "import [options] PATH [REFERENCE]", + Short: "Import a tarball to create a filesystem image", + Long: importDescription, + RunE: importCon, + ValidArgsFunction: completion.AutocompleteDefault, Example: `podman import http://example.com/ctr.tar url-image cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman import -`, } imageImportCommand = &cobra.Command{ - Args: cobra.MinimumNArgs(1), - Use: importCommand.Use, - Short: importCommand.Short, - Long: importCommand.Long, - RunE: importCommand.RunE, + Args: cobra.MinimumNArgs(1), + Use: importCommand.Use, + Short: importCommand.Short, + Long: importCommand.Long, + RunE: importCommand.RunE, + ValidArgsFunction: importCommand.ValidArgsFunction, Example: `podman image import http://example.com/ctr.tar url-image cat ctr.tar | podman -q image import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman image import -`, @@ -49,19 +53,27 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: importCommand, }) - importFlags(importCommand.Flags()) + importFlags(importCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: imageImportCommand, Parent: imageCmd, }) - importFlags(imageImportCommand.Flags()) + importFlags(imageImportCommand) } -func importFlags(flags *pflag.FlagSet) { - flags.StringArrayVarP(&importOpts.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") - flags.StringVarP(&importOpts.Message, "message", "m", "", "Set commit message for imported image") +func importFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + changeFlagName := "change" + flags.StringArrayVarP(&importOpts.Changes, changeFlagName, "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(common.ChangeCmds, " | ")) + _ = cmd.RegisterFlagCompletionFunc(changeFlagName, common.AutocompleteChangeInstructions) + + messageFlagName := "message" + flags.StringVarP(&importOpts.Message, messageFlagName, "m", "", "Set commit message for imported image") + _ = cmd.RegisterFlagCompletionFunc(messageFlagName, completion.AutocompleteNone) + flags.BoolVarP(&importOpts.Quiet, "quiet", "q", false, "Suppress output") flags.StringVar(&importOpts.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file") _ = flags.MarkHidden("signature-policy") diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go index 8f005553d..488f03760 100644 --- a/cmd/podman/images/inspect.go +++ b/cmd/podman/images/inspect.go @@ -1,6 +1,7 @@ package images import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/inspect" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" @@ -10,10 +11,11 @@ import ( var ( // Command: podman image _inspect_ inspectCmd = &cobra.Command{ - Use: "inspect [options] IMAGE [IMAGE...]", - Short: "Display the configuration of an image", - Long: `Displays the low-level information of an image identified by name or ID.`, - RunE: inspectExec, + Use: "inspect [options] IMAGE [IMAGE...]", + Short: "Display the configuration of an image", + Long: `Displays the low-level information of an image identified by name or ID.`, + RunE: inspectExec, + ValidArgsFunction: common.AutocompleteImages, Example: `podman inspect alpine podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`, @@ -29,7 +31,10 @@ func init() { }) inspectOpts = new(entities.InspectOptions) flags := inspectCmd.Flags() - flags.StringVarP(&inspectOpts.Format, "format", "f", "json", "Format the output to a Go template or json") + + formatFlagName := "format" + flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "json", "Format the output to a Go template or json") + _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func inspectExec(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 489b15086..bcb31e6ee 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -10,14 +10,16 @@ import ( "time" "unicode" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/image/v5/docker/reference" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) type listFlagType struct { @@ -34,12 +36,13 @@ type listFlagType struct { var ( // Command: podman image _list_ listCmd = &cobra.Command{ - Use: "list [options] [IMAGE]", - Aliases: []string{"ls"}, - Args: cobra.MaximumNArgs(1), - Short: "List images in local storage", - Long: "Lists images previously pulled to the system or created on the system.", - RunE: images, + Use: "list [options] [IMAGE]", + Aliases: []string{"ls"}, + Args: cobra.MaximumNArgs(1), + Short: "List images in local storage", + Long: "Lists images previously pulled to the system or created on the system.", + RunE: images, + ValidArgsFunction: common.AutocompleteImages, Example: `podman image list --format json podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}" podman image list --filter dangling=true`, @@ -66,18 +69,31 @@ func init() { Command: listCmd, Parent: imageCmd, }) - imageListFlagSet(listCmd.Flags()) + imageListFlagSet(listCmd) } -func imageListFlagSet(flags *pflag.FlagSet) { +func imageListFlagSet(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&listOptions.All, "all", "a", false, "Show all images (default hides intermediate images)") - flags.StringSliceVarP(&listOptions.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") - flags.StringVar(&listFlag.format, "format", "", "Change the output format to JSON or a Go template") + + filterFlagName := "filter" + flags.StringSliceVarP(&listOptions.Filter, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])") + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteImageFilters) + + formatFlagName := "format" + flags.StringVar(&listFlag.format, formatFlagName, "", "Change the output format to JSON or a Go template") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&listFlag.digests, "digests", false, "Show digests") flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings") flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output") flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Display only image IDs") - flags.StringVar(&listFlag.sort, "sort", "created", "Sort by "+sortFields.String()) + + sortFlagName := "sort" + flags.StringVar(&listFlag.sort, sortFlagName, "created", "Sort by "+sortFields.String()) + _ = cmd.RegisterFlagCompletionFunc(sortFlagName, completion.AutocompleteNone) + flags.BoolVarP(&listFlag.history, "history", "", false, "Display the image name history") } @@ -105,10 +121,10 @@ func images(cmd *cobra.Command, args []string) error { return err } switch { - case listFlag.quiet: - return writeID(imgs) case report.IsJSON(listFlag.format): return writeJSON(imgs) + case listFlag.quiet: + return writeID(imgs) default: if cmd.Flag("format").Changed { listFlag.noHeading = true // V1 compatibility @@ -171,9 +187,13 @@ func writeTemplate(imgs []imageReporter) error { } else { row = report.NormalizeFormat(listFlag.format) } + format := parse.EnforceRange(row) + + tmpl, err := template.New("list").Parse(format) + if err != nil { + return err + } - format := "{{range . }}" + row + "{{end}}" - tmpl := template.Must(template.New("list").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index 02f1b3b39..a24f46781 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -8,6 +8,7 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/docker/reference" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" @@ -15,26 +16,27 @@ import ( "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) var ( loadDescription = "Loads an image from a locally stored archive (tar file) into container storage." loadCommand = &cobra.Command{ - Use: "load [options] [NAME[:TAG]]", - Short: "Load an image from container archive", - Long: loadDescription, - RunE: load, - Args: cobra.MaximumNArgs(1), + Use: "load [options] [NAME[:TAG]]", + Short: "Load image(s) from a tar archive", + Long: loadDescription, + RunE: load, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: completion.AutocompleteNone, } imageLoadCommand = &cobra.Command{ - Args: loadCommand.Args, - Use: loadCommand.Use, - Short: loadCommand.Short, - Long: loadCommand.Long, - RunE: loadCommand.RunE, + Args: loadCommand.Args, + Use: loadCommand.Use, + Short: loadCommand.Short, + Long: loadCommand.Long, + ValidArgsFunction: loadCommand.ValidArgsFunction, + RunE: loadCommand.RunE, } ) @@ -47,17 +49,22 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: loadCommand, }) - loadFlags(loadCommand.Flags()) + loadFlags(loadCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: imageLoadCommand, Parent: imageCmd, }) - loadFlags(imageLoadCommand.Flags()) + loadFlags(imageLoadCommand) } -func loadFlags(flags *pflag.FlagSet) { - flags.StringVarP(&loadOpts.Input, "input", "i", "", "Read from specified archive file (default: stdin)") +func loadFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + inputFlagName := "input" + flags.StringVarP(&loadOpts.Input, inputFlagName, "i", "", "Read from specified archive file (default: stdin)") + _ = cmd.RegisterFlagCompletionFunc(inputFlagName, completion.AutocompleteDefault) + flags.BoolVarP(&loadOpts.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadOpts.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file") _ = flags.MarkHidden("signature-policy") diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go index 28e9264ee..1eac59ef9 100644 --- a/cmd/podman/images/mount.go +++ b/cmd/podman/images/mount.go @@ -7,12 +7,12 @@ import ( "text/template" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -24,10 +24,11 @@ var ( ` mountCommand = &cobra.Command{ - Use: "mount [options] [IMAGE...]", - Short: "Mount an image's root filesystem", - Long: mountDescription, - RunE: mount, + Use: "mount [options] [IMAGE...]", + Short: "Mount an image's root filesystem", + Long: mountDescription, + RunE: mount, + ValidArgsFunction: common.AutocompleteImages, Example: `podman image mount imgID podman image mount imgID1 imgID2 imgID3 podman image mount @@ -43,9 +44,14 @@ var ( mountOpts entities.ImageMountOptions ) -func mountFlags(flags *pflag.FlagSet) { +func mountFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all images") - flags.StringVar(&mountOpts.Format, "format", "", "Print the mounted images in specified format (json)") + + formatFlagName := "format" + flags.StringVar(&mountOpts.Format, formatFlagName, "", "Print the mounted images in specified format (json)") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func init() { @@ -54,7 +60,7 @@ func init() { Command: mountCommand, Parent: imageCmd, }) - mountFlags(mountCommand.Flags()) + mountFlags(mountCommand) } func mount(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index b6e6b9562..e68fe5f40 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -6,11 +6,11 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -19,12 +19,13 @@ var ( If an image is not being used by a container, it will be removed from the system.` pruneCmd = &cobra.Command{ - Use: "prune [options]", - Args: validate.NoArgs, - Short: "Remove unused images", - Long: pruneDescription, - RunE: prune, - Example: `podman image prune`, + Use: "prune [options]", + Args: validate.NoArgs, + Short: "Remove unused images", + Long: pruneDescription, + RunE: prune, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman image prune`, } pruneOpts = entities.ImagePruneOptions{} @@ -42,7 +43,11 @@ func init() { flags := pruneCmd.Flags() flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones") flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation") - flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + + filterFlagName := "filter" + flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + //TODO: add completion for filters + _ = pruneCmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) } @@ -54,7 +59,7 @@ WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] `) answer, err := reader.ReadString('\n') if err != nil { - return errors.Wrapf(err, "error reading input") + return err } if strings.ToLower(answer)[0] != 'y' { return nil diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 35ef80f3c..a6f41688c 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -5,13 +5,13 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // pullOptionsWrapper wraps entities.ImagePullOptions and prevents leaking @@ -30,11 +30,12 @@ var ( // Command: podman pull pullCmd = &cobra.Command{ - Use: "pull [options] IMAGE", - Args: cobra.ExactArgs(1), - Short: "Pull an image from a registry", - Long: pullDescription, - RunE: imagePull, + Use: "pull [options] IMAGE", + Args: cobra.ExactArgs(1), + Short: "Pull an image from a registry", + Long: pullDescription, + RunE: imagePull, + ValidArgsFunction: common.AutocompleteImages, Example: `podman pull imageName podman pull fedora:latest`, } @@ -43,11 +44,12 @@ var ( // It's basically a clone of `pullCmd` with the exception of being a // child of the images command. imagesPullCmd = &cobra.Command{ - Use: pullCmd.Use, - Args: pullCmd.Args, - Short: pullCmd.Short, - Long: pullCmd.Long, - RunE: pullCmd.RunE, + Use: pullCmd.Use, + Args: pullCmd.Args, + Short: pullCmd.Short, + Long: pullCmd.Long, + RunE: pullCmd.RunE, + ValidArgsFunction: pullCmd.ValidArgsFunction, Example: `podman image pull imageName podman image pull fedora:latest`, } @@ -59,9 +61,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: pullCmd, }) - - flags := pullCmd.Flags() - pullFlags(flags) + pullFlags(pullCmd) // images pull registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -69,26 +69,46 @@ func init() { Command: imagesPullCmd, Parent: imageCmd, }) - - imagesPullFlags := imagesPullCmd.Flags() - pullFlags(imagesPullFlags) + pullFlags(imagesPullCmd) } // pullFlags set the flags for the pull command. -func pullFlags(flags *pflag.FlagSet) { +func pullFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVar(&pullOptions.AllTags, "all-tags", false, "All tagged images in the repository will be pulled") - flags.StringVar(&pullOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.StringVar(&pullOptions.OverrideArch, "override-arch", "", "Use `ARCH` instead of the architecture of the machine for choosing images") - flags.StringVar(&pullOptions.OverrideOS, "override-os", "", "Use `OS` instead of the running OS for choosing images") - flags.StringVar(&pullOptions.OverrideVariant, "override-variant", "", " use VARIANT instead of the running architecture variant for choosing images") + + credsFlagName := "creds" + flags.StringVar(&pullOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + + overrideArchFlagName := "override-arch" + flags.StringVar(&pullOptions.OverrideArch, overrideArchFlagName, "", "Use `ARCH` instead of the architecture of the machine for choosing images") + _ = cmd.RegisterFlagCompletionFunc(overrideArchFlagName, completion.AutocompleteNone) + + overrideOsFlagName := "override-os" + flags.StringVar(&pullOptions.OverrideOS, overrideOsFlagName, "", "Use `OS` instead of the running OS for choosing images") + _ = cmd.RegisterFlagCompletionFunc(overrideOsFlagName, completion.AutocompleteNone) + + overrideVariantFlagName := "override-variant" + flags.StringVar(&pullOptions.OverrideVariant, overrideVariantFlagName, "", " use VARIANT instead of the running architecture variant for choosing images") + _ = cmd.RegisterFlagCompletionFunc(overrideVariantFlagName, completion.AutocompleteNone) + flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&pullOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.BoolVar(&pullOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - flags.StringVar(&pullOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + + authfileFlagName := "authfile" + flags.StringVar(&pullOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) if !registry.IsRemote() { - flags.StringVar(&pullOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + + certDirFlagName := "cert-dir" + flags.StringVar(&pullOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") + _ = cmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + } _ = flags.MarkHidden("signature-policy") } @@ -104,7 +124,7 @@ func imagePull(cmd *cobra.Command, args []string) error { } if pullOptions.Authfile != "" { if _, err := os.Stat(pullOptions.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", pullOptions.Authfile) + return err } } diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 718bd4e8c..447b02fbe 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -4,13 +4,13 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // pushOptionsWrapper wraps entities.ImagepushOptions and prevents leaking @@ -29,11 +29,12 @@ var ( // Command: podman push pushCmd = &cobra.Command{ - Use: "push [options] SOURCE [DESTINATION]", - Short: "Push an image to a specified destination", - Long: pushDescription, - RunE: imagePush, - Args: cobra.RangeArgs(1, 2), + Use: "push [options] SOURCE [DESTINATION]", + Short: "Push an image to a specified destination", + Long: pushDescription, + RunE: imagePush, + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: common.AutocompleteImages, Example: `podman push imageID docker://registry.example.com/repository:tag podman push imageID oci-archive:/path/to/layout:image:tag`, } @@ -42,11 +43,12 @@ var ( // It's basically a clone of `pushCmd` with the exception of being a // child of the images command. imagePushCmd = &cobra.Command{ - Use: pushCmd.Use, - Short: pushCmd.Short, - Long: pushCmd.Long, - RunE: pushCmd.RunE, - Args: pushCmd.Args, + Use: pushCmd.Use, + Short: pushCmd.Short, + Long: pushCmd.Long, + RunE: pushCmd.RunE, + Args: pushCmd.Args, + ValidArgsFunction: pushCmd.ValidArgsFunction, Example: `podman image push imageID docker://registry.example.com/repository:tag podman image push imageID oci-archive:/path/to/layout:image:tag`, } @@ -58,9 +60,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: pushCmd, }) - - flags := pushCmd.Flags() - pushFlags(flags) + pushFlags(pushCmd) // images push registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -68,23 +68,45 @@ func init() { Command: imagePushCmd, Parent: imageCmd, }) - - pushFlags(imagePushCmd.Flags()) + pushFlags(imagePushCmd) } // pushFlags set the flags for the push command. -func pushFlags(flags *pflag.FlagSet) { - flags.StringVar(&pushOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&pushOptions.CertDir, "cert-dir", "", "Path to a directory containing TLS certificates and keys") +func pushFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + authfileFlagName := "authfile" + flags.StringVar(&pushOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + + certDirFlagName := "cert-dir" + flags.StringVar(&pushOptions.CertDir, certDirFlagName, "", "Path to a directory containing TLS certificates and keys") + _ = cmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + flags.BoolVar(&pushOptions.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)") - flags.StringVar(&pushOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.StringVar(&pushOptions.DigestFile, "digestfile", "", "Write the digest of the pushed image to the specified file") + + credsFlagName := "creds" + flags.StringVar(&pushOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") - flags.StringVarP(&pushOptions.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir' transport (default is manifest type of source)") + + digestfileFlagName := "digestfile" + flags.StringVar(&pushOptions.DigestFile, digestfileFlagName, "", "Write the digest of the pushed image to the specified file") + _ = cmd.RegisterFlagCompletionFunc(digestfileFlagName, completion.AutocompleteDefault) + + formatFlagName := "format" + flags.StringVarP(&pushOptions.Format, formatFlagName, "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir' transport (default is manifest type of source)") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteManifestFormat) + flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images") flags.BoolVar(&pushOptions.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") flags.StringVar(&pushOptions.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file") - flags.StringVar(&pushOptions.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") + + signByFlagName := "sign-by" + flags.StringVar(&pushOptions.SignBy, signByFlagName, "", "Add a signature at the destination using the specified key") + _ = cmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") if registry.IsRemote() { @@ -110,7 +132,7 @@ func imagePush(cmd *cobra.Command, args []string) error { if pushOptions.Authfile != "" { if _, err := os.Stat(pushOptions.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", pushOptions.Authfile) + return err } } diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index 9dddef48f..587f08c29 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -3,6 +3,7 @@ package images import ( "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/errorhandling" @@ -14,10 +15,11 @@ import ( var ( rmDescription = "Removes one or more previously pulled or locally created images." rmCmd = &cobra.Command{ - Use: "rm [options] IMAGE [IMAGE...]", - Short: "Removes one or more images from local storage", - Long: rmDescription, - RunE: rm, + Use: "rm [options] IMAGE [IMAGE...]", + Short: "Removes one or more images from local storage", + Long: rmDescription, + RunE: rm, + ValidArgsFunction: common.AutocompleteImages, Example: `podman image rm imageID podman image rm --force alpine podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`, diff --git a/cmd/podman/images/rmi.go b/cmd/podman/images/rmi.go index 520847890..ab174d750 100644 --- a/cmd/podman/images/rmi.go +++ b/cmd/podman/images/rmi.go @@ -10,12 +10,13 @@ import ( var ( rmiCmd = &cobra.Command{ - Use: strings.Replace(rmCmd.Use, "rm ", "rmi ", 1), - Args: rmCmd.Args, - Short: rmCmd.Short, - Long: rmCmd.Long, - RunE: rmCmd.RunE, - Example: strings.Replace(rmCmd.Example, "podman image rm", "podman rmi", -1), + Use: strings.Replace(rmCmd.Use, "rm ", "rmi ", 1), + Args: rmCmd.Args, + Short: rmCmd.Short, + Long: rmCmd.Long, + RunE: rmCmd.RunE, + ValidArgsFunction: rmCmd.ValidArgsFunction, + Example: strings.Replace(rmCmd.Example, "podman image rm", "podman rmi", -1), } ) diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index db1fa7159..9ef2d0c91 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -5,6 +5,8 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/libpod/define" @@ -12,7 +14,6 @@ import ( "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -42,16 +43,18 @@ var ( } return nil }, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman save --quiet -o myimage.tar imageID podman save --format docker-dir -o ubuntu-dir ubuntu podman save > alpine-all.tar alpine:latest`, } imageSaveCommand = &cobra.Command{ - Args: saveCommand.Args, - Use: saveCommand.Use, - Short: saveCommand.Short, - Long: saveCommand.Long, - RunE: saveCommand.RunE, + Args: saveCommand.Args, + Use: saveCommand.Use, + Short: saveCommand.Short, + Long: saveCommand.Long, + RunE: saveCommand.RunE, + ValidArgsFunction: saveCommand.ValidArgsFunction, Example: `podman image save --quiet -o myimage.tar imageID podman image save --format docker-dir -o ubuntu-dir ubuntu podman image save > alpine-all.tar alpine:latest`, @@ -67,20 +70,29 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: saveCommand, }) - saveFlags(saveCommand.Flags()) + saveFlags(saveCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: imageSaveCommand, Parent: imageCmd, }) - saveFlags(imageSaveCommand.Flags()) + saveFlags(imageSaveCommand) } -func saveFlags(flags *pflag.FlagSet) { +func saveFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVar(&saveOpts.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") - flags.StringVar(&saveOpts.Format, "format", define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") - flags.StringVarP(&saveOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") + + formatFlagName := "format" + flags.StringVar(&saveOpts.Format, formatFlagName, define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteImageSaveFormat) + + outputFlagName := "output" + flags.StringVarP(&saveOpts.Output, outputFlagName, "o", "", "Write to a specified file (default: stdout, which must be redirected)") + _ = cmd.RegisterFlagCompletionFunc(outputFlagName, completion.AutocompleteDefault) + flags.BoolVarP(&saveOpts.Quiet, "quiet", "q", false, "Suppress the output") flags.BoolVarP(&saveOpts.MultiImageArchive, "multi-image-archive", "m", containerConfig.Engine.MultiImageArchive, "Interpret additional arguments as images not tags and create a multi-image-archive (only for docker-archive)") } diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index b1a1442a6..c2ef7d767 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -1,18 +1,20 @@ package images import ( + "fmt" "os" "text/tabwriter" "text/template" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // searchOptionsWrapper wraps entities.ImagePullOptions and prevents leaking @@ -32,11 +34,12 @@ var ( // Command: podman search searchCmd = &cobra.Command{ - Use: "search [options] TERM", - Short: "Search registry for image", - Long: searchDescription, - RunE: imageSearch, - Args: cobra.ExactArgs(1), + Use: "search [options] TERM", + Short: "Search registry for image", + Long: searchDescription, + RunE: imageSearch, + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.AutocompleteNone, Example: `podman search --filter=is-official --limit 3 alpine podman search registry.fedoraproject.org/ # only works with v2 registries podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, @@ -44,12 +47,13 @@ var ( // Command: podman image search imageSearchCmd = &cobra.Command{ - Use: searchCmd.Use, - Short: searchCmd.Short, - Long: searchCmd.Long, - RunE: searchCmd.RunE, - Args: searchCmd.Args, - Annotations: searchCmd.Annotations, + Use: searchCmd.Use, + Short: searchCmd.Short, + Long: searchCmd.Long, + RunE: searchCmd.RunE, + Args: searchCmd.Args, + Annotations: searchCmd.Annotations, + ValidArgsFunction: searchCmd.ValidArgsFunction, Example: `podman image search --filter=is-official --limit 3 alpine podman image search registry.fedoraproject.org/ # only works with v2 registries podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, @@ -62,9 +66,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: searchCmd, }) - - flags := searchCmd.Flags() - searchFlags(flags) + searchFlags(searchCmd) // images search registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -72,18 +74,32 @@ func init() { Command: imageSearchCmd, Parent: imageCmd, }) - - imageSearchFlags := imageSearchCmd.Flags() - searchFlags(imageSearchFlags) + searchFlags(imageSearchCmd) } // searchFlags set the flags for the pull command. -func searchFlags(flags *pflag.FlagSet) { - flags.StringSliceVarP(&searchOptions.Filters, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") - flags.StringVar(&searchOptions.Format, "format", "", "Change the output format to a Go template") - flags.IntVar(&searchOptions.Limit, "limit", 0, "Limit the number of results") +func searchFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + filterFlagName := "filter" + flags.StringSliceVarP(&searchOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])") + //TODO add custom filter function + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + + formatFlagName := "format" + flags.StringVar(&searchOptions.Format, formatFlagName, "", "Change the output format to JSON or a Go template") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, completion.AutocompleteNone) + + limitFlagName := "limit" + flags.IntVar(&searchOptions.Limit, limitFlagName, 0, "Limit the number of results") + _ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone) + flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") - flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + + authfileFlagName := "authfile" + flags.StringVar(&searchOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") flags.BoolVar(&searchOptions.ListTags, "list-tags", false, "List the tags of the input registry") } @@ -98,10 +114,6 @@ func imageSearch(cmd *cobra.Command, args []string) error { return errors.Errorf("search requires exactly one argument") } - if searchOptions.Limit > 100 { - return errors.Errorf("Limit %d is outside the range of [1, 100]", searchOptions.Limit) - } - if searchOptions.ListTags && len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } @@ -116,7 +128,7 @@ func imageSearch(cmd *cobra.Command, args []string) error { if searchOptions.Authfile != "" { if _, err := os.Stat(searchOptions.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", searchOptions.Authfile) + return err } } @@ -130,26 +142,37 @@ func imageSearch(cmd *cobra.Command, args []string) error { } hdrs := report.Headers(entities.ImageSearchReport{}, nil) - row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" - if searchOptions.ListTags { + renderHeaders := true + var row string + switch { + case searchOptions.ListTags: if len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } row = "{{.Name}}\t{{.Tag}}\n" - } - if cmd.Flags().Changed("format") { + case report.IsJSON(searchOptions.Format): + prettyJSON, err := json.MarshalIndent(searchReport, "", " ") + if err != nil { + return err + } + fmt.Println(string(prettyJSON)) + return nil + case cmd.Flags().Changed("format"): + renderHeaders = parse.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) + default: + row = "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" } - row = "{{range .}}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("search").Parse(row) + tmpl, err := template.New("search").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if !cmd.Flags().Changed("format") { + if renderHeaders { if err := tmpl.Execute(w, hdrs); err != nil { return errors.Wrapf(err, "failed to write search column headers") } diff --git a/cmd/podman/images/sign.go b/cmd/podman/images/sign.go index f6c1f9856..342536f7c 100644 --- a/cmd/podman/images/sign.go +++ b/cmd/podman/images/sign.go @@ -3,6 +3,8 @@ package images import ( "os" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -12,11 +14,12 @@ import ( var ( signDescription = "Create a signature file that can be used later to verify the image." signCommand = &cobra.Command{ - Use: "sign [options] IMAGE [IMAGE...]", - Short: "Sign an image", - Long: signDescription, - RunE: sign, - Args: cobra.MinimumNArgs(1), + Use: "sign [options] IMAGE [IMAGE...]", + Short: "Sign an image", + Long: signDescription, + RunE: sign, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: common.AutocompleteImages, Example: `podman image sign --sign-by mykey imageID podman image sign --sign-by mykey --directory ./mykeydir imageID`, } @@ -33,9 +36,17 @@ func init() { Parent: imageCmd, }) flags := signCommand.Flags() - flags.StringVarP(&signOptions.Directory, "directory", "d", "", "Define an alternate directory to store signatures") - flags.StringVar(&signOptions.SignBy, "sign-by", "", "Name of the signing key") - flags.StringVar(&signOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + directoryFlagName := "directory" + flags.StringVarP(&signOptions.Directory, directoryFlagName, "d", "", "Define an alternate directory to store signatures") + _ = signCommand.RegisterFlagCompletionFunc(directoryFlagName, completion.AutocompleteDefault) + + signByFlagName := "sign-by" + flags.StringVar(&signOptions.SignBy, signByFlagName, "", "Name of the signing key") + _ = signCommand.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + + certDirFlagName := "cert-dir" + flags.StringVar(&signOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") + _ = signCommand.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) } func sign(cmd *cobra.Command, args []string) error { @@ -47,7 +58,7 @@ func sign(cmd *cobra.Command, args []string) error { if len(signOptions.Directory) > 0 { sigStoreDir = signOptions.Directory if _, err := os.Stat(sigStoreDir); err != nil { - return errors.Wrapf(err, "invalid directory %s", sigStoreDir) + return err } } _, err := registry.ImageEngine().Sign(registry.Context(), args, signOptions) diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go index be7f84588..157db49ca 100644 --- a/cmd/podman/images/tag.go +++ b/cmd/podman/images/tag.go @@ -1,6 +1,7 @@ package images import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -15,6 +16,7 @@ var ( RunE: tag, Args: cobra.MinimumNArgs(2), DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteImages, Example: `podman tag 0e3bbc2 fedora:latest podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, @@ -27,6 +29,7 @@ var ( Short: tagCommand.Short, Long: tagCommand.Long, RunE: tagCommand.RunE, + ValidArgsFunction: tagCommand.ValidArgsFunction, Example: `podman image tag 0e3bbc2 fedora:latest podman image tag imageID:latest myNewImage:newTag podman image tag httpd myregistryhost:5000/fedora/httpd:v2`, diff --git a/cmd/podman/images/tree.go b/cmd/podman/images/tree.go index 237a2ab91..76c69b37e 100644 --- a/cmd/podman/images/tree.go +++ b/cmd/podman/images/tree.go @@ -3,6 +3,7 @@ package images import ( "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -11,12 +12,13 @@ import ( var ( treeDescription = "Prints layer hierarchy of an image in a tree format" treeCmd = &cobra.Command{ - Use: "tree [options] IMAGE", - Args: cobra.ExactArgs(1), - Short: treeDescription, - Long: treeDescription, - RunE: tree, - Example: "podman image tree alpine:latest", + Use: "tree [options] IMAGE", + Args: cobra.ExactArgs(1), + Short: treeDescription, + Long: treeDescription, + RunE: tree, + ValidArgsFunction: common.AutocompleteImages, + Example: "podman image tree alpine:latest", } treeOpts entities.ImageTreeOptions ) diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go index 2e4b4fe17..1a7392f3e 100644 --- a/cmd/podman/images/trust_set.go +++ b/cmd/podman/images/trust_set.go @@ -1,6 +1,8 @@ package images import ( + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/domain/entities" @@ -12,12 +14,13 @@ import ( var ( setTrustDescription = "Set default trust policy or add a new trust policy for a registry" setTrustCommand = &cobra.Command{ - Use: "set [options] REGISTRY", - Short: "Set default trust policy or a new trust policy for a registry", - Long: setTrustDescription, - Example: "", - RunE: setTrust, - Args: cobra.ExactArgs(1), + Use: "set [options] REGISTRY", + Short: "Set default trust policy or a new trust policy for a registry", + Long: setTrustDescription, + Example: "", + RunE: setTrust, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteRegistries, } ) @@ -34,11 +37,17 @@ func init() { setFlags := setTrustCommand.Flags() setFlags.StringVar(&setOptions.PolicyPath, "policypath", "", "") _ = setFlags.MarkHidden("policypath") - setFlags.StringSliceVarP(&setOptions.PubKeysFile, "pubkeysfile", "f", []string{}, `Path of installed public key(s) to trust for TARGET. + + pubkeysfileFlagName := "pubkeysfile" + setFlags.StringSliceVarP(&setOptions.PubKeysFile, pubkeysfileFlagName, "f", []string{}, `Path of installed public key(s) to trust for TARGET. Absolute path to keys is added to policy.json. May used multiple times to define multiple public keys. File(s) must exist before using this command`) - setFlags.StringVarP(&setOptions.Type, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") + _ = setTrustCommand.RegisterFlagCompletionFunc(pubkeysfileFlagName, completion.AutocompleteDefault) + + typeFlagName := "type" + setFlags.StringVarP(&setOptions.Type, typeFlagName, "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") + _ = setTrustCommand.RegisterFlagCompletionFunc(typeFlagName, common.AutocompleteTrustType) } func setTrust(cmd *cobra.Command, args []string) error { @@ -46,7 +55,7 @@ func setTrust(cmd *cobra.Command, args []string) error { valid, err := image.IsValidImageURI(args[0]) if err != nil || !valid { - return errors.Wrapf(err, "invalid image uri %s", args[0]) + return err } if !util.StringInSlice(setOptions.Type, validTrustTypes) { diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go index ba3b4e7fb..dc35dc6a1 100644 --- a/cmd/podman/images/trust_show.go +++ b/cmd/podman/images/trust_show.go @@ -6,6 +6,7 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -14,12 +15,13 @@ import ( var ( showTrustDescription = "Display trust policy for the system" showTrustCommand = &cobra.Command{ - Use: "show [options] [REGISTRY]", - Short: "Display trust policy for the system", - Long: showTrustDescription, - RunE: showTrust, - Args: cobra.MaximumNArgs(1), - Example: "", + Use: "show [options] [REGISTRY]", + Short: "Display trust policy for the system", + Long: showTrustDescription, + RunE: showTrust, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: common.AutocompleteRegistries, + Example: "", } ) diff --git a/cmd/podman/images/unmount.go b/cmd/podman/images/unmount.go index 50dc972e8..3af959b9f 100644 --- a/cmd/podman/images/unmount.go +++ b/cmd/podman/images/unmount.go @@ -3,6 +3,7 @@ package images import ( "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -19,11 +20,12 @@ var ( An unmount can be forced with the --force flag. ` unmountCommand = &cobra.Command{ - Use: "unmount [options] IMAGE [IMAGE...]", - Aliases: []string{"umount"}, - Short: "Unmount an image's root filesystem", - Long: description, - RunE: unmount, + Use: "unmount [options] IMAGE [IMAGE...]", + Aliases: []string{"umount"}, + Short: "Unmount an image's root filesystem", + Long: description, + RunE: unmount, + ValidArgsFunction: common.AutocompleteImages, Example: `podman unmount imgID podman unmount imgID1 imgID2 imgID3 podman unmount --all`, diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index da749c8a5..17dc21203 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -1,6 +1,7 @@ package images import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -14,6 +15,7 @@ var ( RunE: untag, Args: cobra.MinimumNArgs(1), DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteImages, Example: `podman untag 0e3bbc2 podman untag imageID:latest otherImageName:latest podman untag httpd myregistryhost:5000/fedora/httpd:v2`, @@ -26,6 +28,7 @@ var ( Short: untagCommand.Short, Long: untagCommand.Long, RunE: untagCommand.RunE, + ValidArgsFunction: untagCommand.ValidArgsFunction, Example: `podman image untag 0e3bbc2 podman image untag imageID:latest otherImageName:latest podman image untag httpd myregistryhost:5000/fedora/httpd:v2`, diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index f1d673a21..f62abe931 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -1,6 +1,7 @@ package main import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/inspect" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" @@ -19,11 +20,12 @@ var ( // Command: podman _inspect_ Object_ID inspectCmd = &cobra.Command{ - Use: "inspect [options] {CONTAINER_ID | IMAGE_ID} [...]", - Short: "Display the configuration of object denoted by ID", - RunE: inspectExec, - Long: inspectDescription, - TraverseChildren: true, + Use: "inspect [options] {CONTAINER_ID | IMAGE_ID} [...]", + Short: "Display the configuration of object denoted by ID", + RunE: inspectExec, + Long: inspectDescription, + TraverseChildren: true, + ValidArgsFunction: common.AutocompleteContainersAndImages, Example: `podman inspect fedora podman inspect --type image fedora podman inspect CtrID ImgID diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index 9c400d506..13f36ebbd 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -2,6 +2,7 @@ package inspect import ( "context" + "encoding/json" // due to a bug in json-iterator it cannot be used here "fmt" "os" "regexp" @@ -9,7 +10,9 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -26,17 +29,14 @@ const ( ContainerType = "container" // ImageType is the image type. ImageType = "image" - //NetworkType is the network type + // NetworkType is the network type NetworkType = "network" - //PodType is the pod type. + // PodType is the pod type. PodType = "pod" - //VolumeType is the volume type + // VolumeType is the volume type VolumeType = "volume" ) -// Pull in configured json library -var json = registry.JSONLibrary() - // AddInspectFlagSet takes a command and adds the inspect flags and returns an // InspectOptions object. func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { @@ -44,8 +44,14 @@ func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { flags := cmd.Flags() flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") - flags.StringVarP(&opts.Format, "format", "f", "json", "Format the output to a Go template or json") - flags.StringVarP(&opts.Type, "type", "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType)) + + formatFlagName := "format" + flags.StringVarP(&opts.Format, formatFlagName, "f", "json", "Format the output to a Go template or json") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, completion.AutocompleteNone) + + typeFlagName := "type" + flags.StringVarP(&opts.Type, typeFlagName, "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType)) + _ = cmd.RegisterFlagCompletionFunc(typeFlagName, common.AutocompleteInspectType) validate.AddLatestFlag(cmd, &opts.Latest) return &opts @@ -165,7 +171,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { data = append(data, podData) } } - if i.podOptions.Latest { //latest means there are no names in the namesOrID array + if i.podOptions.Latest { // latest means there are no names in the namesOrID array podData, err := i.containerEngine.PodInspect(ctx, i.podOptions) if err != nil { cause := errors.Cause(err) @@ -230,9 +236,12 @@ func (i *inspector) inspect(namesOrIDs []string) error { } func printJSON(data []interface{}) error { - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - return enc.Encode(data) + buf, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + _, err = fmt.Println(string(buf)) + return err } func printTmpl(typ, row string, data []interface{}) error { diff --git a/cmd/podman/login.go b/cmd/podman/login.go index a789cef33..7c8fe03dc 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -5,7 +5,9 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/registries" @@ -20,11 +22,12 @@ type loginOptionsWrapper struct { var ( loginOptions = loginOptionsWrapper{} loginCommand = &cobra.Command{ - Use: "login [options] [REGISTRY]", - Short: "Login to a container registry", - Long: "Login to a container registry on a specified server.", - RunE: login, - Args: cobra.MaximumNArgs(1), + Use: "login [options] [REGISTRY]", + Short: "Login to a container registry", + Long: "Login to a container registry on a specified server.", + RunE: login, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: common.AutocompleteRegistries, Example: `podman login quay.io podman login --username ... --password ... quay.io podman login --authfile dir/auth.json quay.io`, @@ -44,6 +47,9 @@ func init() { // Flags from the auth package. flags.AddFlagSet(auth.GetLoginFlags(&loginOptions.LoginOptions)) + // Add flag completion + completion.CompleteCommandFlags(loginCommand, auth.GetLoginFlagsCompletions()) + // Podman flags. flags.BoolVarP(&loginOptions.tlsVerify, "tls-verify", "", false, "Require HTTPS and verify certificates when contacting registries") loginOptions.Stdin = os.Stdin diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 7b5615d30..3cb11071b 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -4,7 +4,9 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/registries" @@ -14,11 +16,12 @@ import ( var ( logoutOptions = auth.LogoutOptions{} logoutCommand = &cobra.Command{ - Use: "logout [options] [REGISTRY]", - Short: "Logout of a container registry", - Long: "Remove the cached username and password for the registry.", - RunE: logout, - Args: cobra.MaximumNArgs(1), + Use: "logout [options] [REGISTRY]", + Short: "Logout of a container registry", + Long: "Remove the cached username and password for the registry.", + RunE: logout, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: common.AutocompleteRegistries, Example: `podman logout quay.io podman logout --authfile dir/auth.json quay.io podman logout --all`, @@ -37,6 +40,10 @@ func init() { // Flags from the auth package. flags.AddFlagSet(auth.GetLogoutFlags(&logoutOptions)) + + // Add flag completion + completion.CompleteCommandFlags(logoutCommand, auth.GetLogoutFlagsCompletions()) + logoutOptions.Stdout = os.Stdout logoutOptions.AcceptUnspecifiedRegistry = true } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 9747769c7..c3aaf84a8 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + _ "github.com/containers/podman/v2/cmd/podman/completion" _ "github.com/containers/podman/v2/cmd/podman/containers" _ "github.com/containers/podman/v2/cmd/podman/generate" _ "github.com/containers/podman/v2/cmd/podman/healthcheck" @@ -35,6 +36,13 @@ func main() { os.Setenv("TMPDIR", "/var/tmp") } + rootCmd = parseCommands() + + Execute() + os.Exit(0) +} + +func parseCommands() *cobra.Command { cfg := registry.PodmanConfig() for _, c := range registry.Commands { for _, m := range c.Mode { @@ -75,6 +83,5 @@ func main() { os.Exit(1) } - Execute() - os.Exit(0) + return rootCmd } diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go index 4b85f4c2a..cb0838eeb 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -25,10 +26,11 @@ type manifestAddOptsWrapper struct { var ( manifestAddOpts = manifestAddOptsWrapper{} addCmd = &cobra.Command{ - Use: "add [options] LIST LIST", - Short: "Add images to a manifest list or image index", - Long: "Adds an image to a manifest list or image index.", - RunE: add, + Use: "add [options] LIST LIST", + Short: "Add images to a manifest list or image index", + Long: "Adds an image to a manifest list or image index.", + RunE: add, + ValidArgsFunction: common.AutocompleteImages, Example: `podman manifest add mylist:v1.11 image:v1.11-amd64 podman manifest add mylist:v1.11 transport:imageName`, Args: cobra.ExactArgs(2), @@ -43,17 +45,44 @@ func init() { }) flags := addCmd.Flags() flags.BoolVar(&manifestAddOpts.All, "all", false, "add all of the list's images if the image is a list") - flags.StringSliceVar(&manifestAddOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image") - flags.StringVar(&manifestAddOpts.Arch, "arch", "", "override the `architecture` of the specified image") - flags.StringVar(&manifestAddOpts.Authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&manifestAddOpts.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") - flags.StringVar(&manifestAddOpts.CredentialsCLI, "creds", "", "use `[username[:password]]` for accessing the registry") - - flags.StringSliceVar(&manifestAddOpts.Features, "features", nil, "override the `features` of the specified image") - flags.StringVar(&manifestAddOpts.OS, "os", "", "override the `OS` of the specified image") - flags.StringVar(&manifestAddOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image") + + annotationFlagName := "annotation" + flags.StringSliceVar(&manifestAddOpts.Annotation, annotationFlagName, nil, "set an `annotation` for the specified image") + _ = addCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) + + archFlagName := "arch" + flags.StringVar(&manifestAddOpts.Arch, archFlagName, "", "override the `architecture` of the specified image") + _ = addCmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteNone) + + authfileFlagName := "authfile" + flags.StringVar(&manifestAddOpts.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = addCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + + certDirFlagName := "cert-dir" + flags.StringVar(&manifestAddOpts.CertDir, certDirFlagName, "", "use certificates at the specified path to access the registry") + _ = addCmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + + credsFlagName := "creds" + flags.StringVar(&manifestAddOpts.CredentialsCLI, credsFlagName, "", "use `[username[:password]]` for accessing the registry") + _ = addCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + + featuresFlagName := "features" + flags.StringSliceVar(&manifestAddOpts.Features, featuresFlagName, nil, "override the `features` of the specified image") + _ = addCmd.RegisterFlagCompletionFunc(featuresFlagName, completion.AutocompleteNone) + + osFlagName := "os" + flags.StringVar(&manifestAddOpts.OS, osFlagName, "", "override the `OS` of the specified image") + _ = addCmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteNone) + + osVersionFlagName := "os-version" + flags.StringVar(&manifestAddOpts.OSVersion, osVersionFlagName, "", "override the OS `version` of the specified image") + _ = addCmd.RegisterFlagCompletionFunc(osVersionFlagName, completion.AutocompleteNone) + flags.BoolVar(&manifestAddOpts.TLSVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") - flags.StringVar(&manifestAddOpts.Variant, "variant", "", "override the `Variant` of the specified image") + + variantFlagName := "variant" + flags.StringVar(&manifestAddOpts.Variant, variantFlagName, "", "override the `Variant` of the specified image") + _ = addCmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) if registry.IsRemote() { _ = flags.MarkHidden("cert-dir") @@ -86,7 +115,7 @@ func add(cmd *cobra.Command, args []string) error { listID, err := registry.ImageEngine().ManifestAdd(context.Background(), manifestAddOpts.ManifestAddOptions) if err != nil { - return errors.Wrapf(err, "error adding to manifest list %s", args[0]) + return err } fmt.Printf("%s\n", listID) return nil diff --git a/cmd/podman/manifest/annotate.go b/cmd/podman/manifest/annotate.go index 861e94034..71017e0ec 100644 --- a/cmd/podman/manifest/annotate.go +++ b/cmd/podman/manifest/annotate.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -13,12 +15,13 @@ import ( var ( manifestAnnotateOpts = entities.ManifestAnnotateOptions{} annotateCmd = &cobra.Command{ - Use: "annotate [options] LIST IMAGE", - Short: "Add or update information about an entry in a manifest list or image index", - Long: "Adds or updates information about an entry in a manifest list or image index.", - RunE: annotate, - Example: `podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`, - Args: cobra.ExactArgs(2), + Use: "annotate [options] LIST IMAGE", + Short: "Add or update information about an entry in a manifest list or image index", + Long: "Adds or updates information about an entry in a manifest list or image index.", + RunE: annotate, + Example: `podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`, + Args: cobra.ExactArgs(2), + ValidArgsFunction: common.AutocompleteImages, } ) @@ -29,13 +32,34 @@ func init() { Parent: manifestCmd, }) flags := annotateCmd.Flags() - flags.StringSliceVar(&manifestAnnotateOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image") - flags.StringVar(&manifestAnnotateOpts.Arch, "arch", "", "override the `architecture` of the specified image") - flags.StringSliceVar(&manifestAnnotateOpts.Features, "features", nil, "override the `features` of the specified image") - flags.StringVar(&manifestAnnotateOpts.OS, "os", "", "override the `OS` of the specified image") - flags.StringSliceVar(&manifestAnnotateOpts.OSFeatures, "os-features", nil, "override the OS `features` of the specified image") - flags.StringVar(&manifestAnnotateOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image") - flags.StringVar(&manifestAnnotateOpts.Variant, "variant", "", "override the `variant` of the specified image") + + annotationFlagName := "annotation" + flags.StringSliceVar(&manifestAnnotateOpts.Annotation, annotationFlagName, nil, "set an `annotation` for the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) + + archFlagName := "arch" + flags.StringVar(&manifestAnnotateOpts.Arch, archFlagName, "", "override the `architecture` of the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteNone) + + featuresFlagName := "features" + flags.StringSliceVar(&manifestAnnotateOpts.Features, featuresFlagName, nil, "override the `features` of the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(featuresFlagName, completion.AutocompleteNone) + + osFlagName := "os" + flags.StringVar(&manifestAnnotateOpts.OS, osFlagName, "", "override the `OS` of the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteNone) + + osFeaturesFlagName := "os-features" + flags.StringSliceVar(&manifestAnnotateOpts.OSFeatures, osFeaturesFlagName, nil, "override the OS `features` of the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(osFeaturesFlagName, completion.AutocompleteNone) + + osVersionFlagName := "os-version" + flags.StringVar(&manifestAnnotateOpts.OSVersion, osVersionFlagName, "", "override the OS `version` of the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(osVersionFlagName, completion.AutocompleteNone) + + variantFlagName := "variant" + flags.StringVar(&manifestAnnotateOpts.Variant, variantFlagName, "", "override the `Variant` of the specified image") + _ = annotateCmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) } func annotate(cmd *cobra.Command, args []string) error { @@ -49,7 +73,7 @@ func annotate(cmd *cobra.Command, args []string) error { } updatedListID, err := registry.ImageEngine().ManifestAnnotate(context.Background(), args, manifestAnnotateOpts) if err != nil { - return errors.Wrapf(err, "error removing from manifest list %s", listImageSpec) + return err } fmt.Printf("%s\n", updatedListID) return nil diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go index 956946f9d..399f9440c 100644 --- a/cmd/podman/manifest/create.go +++ b/cmd/podman/manifest/create.go @@ -4,19 +4,20 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( manifestCreateOpts = entities.ManifestCreateOptions{} createCmd = &cobra.Command{ - Use: "create [options] LIST [IMAGE]", - Short: "Create manifest list or image index", - Long: "Creates manifest lists or image indexes.", - RunE: create, + Use: "create [options] LIST [IMAGE]", + Short: "Create manifest list or image index", + Long: "Creates manifest lists or image indexes.", + RunE: create, + ValidArgsFunction: common.AutocompleteImages, Example: `podman manifest create mylist:v1.11 podman manifest create mylist:v1.11 arch-specific-image-to-add podman manifest create --all mylist:v1.11 transport:tagged-image-to-add`, @@ -37,7 +38,7 @@ func init() { func create(cmd *cobra.Command, args []string) error { imageID, err := registry.ImageEngine().ManifestCreate(context.Background(), args[:1], args[1:], manifestCreateOpts) if err != nil { - return errors.Wrapf(err, "error creating manifest %s", args[0]) + return err } fmt.Printf("%s\n", imageID) return nil diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go index 19396ceb3..39fd54445 100644 --- a/cmd/podman/manifest/inspect.go +++ b/cmd/podman/manifest/inspect.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -16,6 +16,7 @@ var ( Short: "Display the contents of a manifest list or image index", Long: "Display the contents of a manifest list or image index.", RunE: inspect, + ValidArgsFunction: common.AutocompleteImages, Example: "podman manifest inspect localhost/list", Args: cobra.ExactArgs(1), DisableFlagsInUseLine: true, @@ -33,7 +34,7 @@ func init() { func inspect(cmd *cobra.Command, args []string) error { buf, err := registry.ImageEngine().ManifestInspect(context.Background(), args[0]) if err != nil { - return errors.Wrapf(err, "error inspect manifest %s", args[0]) + return err } fmt.Printf("%s\n", buf) return nil diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go index c3bcdc8c7..990ad0e95 100644 --- a/cmd/podman/manifest/manifest.go +++ b/cmd/podman/manifest/manifest.go @@ -18,7 +18,7 @@ var ( podman manifest create localhost/list podman manifest inspect localhost/list podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64 - podman manifest push mylist:v1.11 quay.io/myimagelist + podman manifest push mylist:v1.11 docker://quay.io/myuser/image:v1.11 podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, } ) diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index 91881c1b3..a3b469491 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -2,7 +2,9 @@ package manifest import ( "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" @@ -22,12 +24,13 @@ type manifestPushOptsWrapper struct { var ( manifestPushOpts = manifestPushOptsWrapper{} pushCmd = &cobra.Command{ - Use: "push [options] SOURCE DESTINATION", - Short: "Push a manifest list or image index to a registry", - Long: "Pushes manifest lists and image indexes to registries.", - RunE: push, - Example: `podman manifest push mylist:v1.11 quay.io/myimagelist`, - Args: cobra.ExactArgs(2), + Use: "push [options] SOURCE DESTINATION", + Short: "Push a manifest list or image index to a registry", + Long: "Pushes manifest lists and image indexes to registries.", + RunE: push, + Example: `podman manifest push mylist:v1.11 docker://quay.io/myuser/image:v1.11`, + Args: cobra.ExactArgs(2), + ValidArgsFunction: common.AutocompleteImages, } ) @@ -40,13 +43,33 @@ func init() { flags := pushCmd.Flags() flags.BoolVar(&manifestPushOpts.Purge, "purge", false, "remove the manifest list if push succeeds") flags.BoolVar(&manifestPushOpts.All, "all", false, "also push the images in the list") - flags.StringVar(&manifestPushOpts.Authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - flags.StringVar(&manifestPushOpts.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") - flags.StringVar(&manifestPushOpts.CredentialsCLI, "creds", "", "use `[username[:password]]` for accessing the registry") - flags.StringVar(&manifestPushOpts.DigestFile, "digestfile", "", "after copying the image, write the digest of the resulting digest to the file") - flags.StringVarP(&manifestPushOpts.Format, "format", "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)") + + authfileFlagName := "authfile" + flags.StringVar(&manifestPushOpts.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = pushCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + + certDirFlagName := "cert-dir" + flags.StringVar(&manifestPushOpts.CertDir, certDirFlagName, "", "use certificates at the specified path to access the registry") + _ = pushCmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + + credsFlagName := "creds" + flags.StringVar(&manifestPushOpts.CredentialsCLI, credsFlagName, "", "use `[username[:password]]` for accessing the registry") + _ = pushCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + + digestfileFlagName := "digestfile" + flags.StringVar(&manifestPushOpts.DigestFile, digestfileFlagName, "", "after copying the image, write the digest of the resulting digest to the file") + _ = pushCmd.RegisterFlagCompletionFunc(digestfileFlagName, completion.AutocompleteDefault) + + formatFlagName := "format" + flags.StringVarP(&manifestPushOpts.Format, formatFlagName, "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)") + _ = pushCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteManifestFormat) + flags.BoolVarP(&manifestPushOpts.RemoveSignatures, "remove-signatures", "", false, "don't copy signatures when pushing images") - flags.StringVar(&manifestPushOpts.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") + + signByFlagName := "sign-by" + flags.StringVar(&manifestPushOpts.SignBy, signByFlagName, "", "sign the image using a GPG key with the specified `FINGERPRINT`") + _ = pushCmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + flags.BoolVar(&manifestPushOpts.TLSVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") @@ -85,7 +108,7 @@ func push(cmd *cobra.Command, args []string) error { manifestPushOpts.SkipTLSVerify = types.NewOptionalBool(!manifestPushOpts.TLSVerifyCLI) } if err := registry.ImageEngine().ManifestPush(registry.Context(), args, manifestPushOpts.ManifestPushOptions); err != nil { - return errors.Wrapf(err, "error pushing manifest %s to %s", listImageSpec, destSpec) + return err } return nil } diff --git a/cmd/podman/manifest/remove.go b/cmd/podman/manifest/remove.go index 677d45484..170e68d3f 100644 --- a/cmd/podman/manifest/remove.go +++ b/cmd/podman/manifest/remove.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -16,6 +17,7 @@ var ( Short: "Remove an entry from a manifest list or image index", Long: "Removes an image from a manifest list or image index.", RunE: remove, + ValidArgsFunction: common.AutocompleteImages, Example: `podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, Args: cobra.ExactArgs(2), DisableFlagsInUseLine: true, diff --git a/cmd/podman/networks/connect.go b/cmd/podman/networks/connect.go new file mode 100644 index 000000000..8afc0c7c0 --- /dev/null +++ b/cmd/podman/networks/connect.go @@ -0,0 +1,47 @@ +package network + +import ( + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + networkConnectDescription = `Add container to a network` + networkConnectCommand = &cobra.Command{ + Use: "connect [options] NETWORK CONTAINER", + Short: "network connect", + Long: networkConnectDescription, + RunE: networkConnect, + Example: `podman network connect web secondary`, + Args: cobra.ExactArgs(2), + ValidArgsFunction: common.AutocompleteNetworkConnectCmd, + } +) + +var ( + networkConnectOptions entities.NetworkConnectOptions +) + +func networkConnectFlags(cmd *cobra.Command) { + flags := cmd.Flags() + aliasFlagName := "alias" + flags.StringSliceVar(&networkConnectOptions.Aliases, aliasFlagName, []string{}, "network scoped alias for container") + _ = cmd.RegisterFlagCompletionFunc(aliasFlagName, completion.AutocompleteNone) +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: networkConnectCommand, + Parent: networkCmd, + }) + networkConnectFlags(networkConnectCommand) +} + +func networkConnect(cmd *cobra.Command, args []string) error { + networkConnectOptions.Container = args[1] + return registry.ContainerEngine().NetworkConnect(registry.Context(), args[0], networkConnectOptions) +} diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go index 74646090d..17de2c95d 100644 --- a/cmd/podman/networks/create.go +++ b/cmd/podman/networks/create.go @@ -4,22 +4,24 @@ import ( "fmt" "net" + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( networkCreateDescription = `create CNI networks for containers and pods` networkCreateCommand = &cobra.Command{ - Use: "create [options] [NETWORK]", - Short: "network create", - Long: networkCreateDescription, - RunE: networkCreate, - Args: cobra.MaximumNArgs(1), - Example: `podman network create podman1`, + Use: "create [options] [NETWORK]", + Short: "network create", + Long: networkCreateDescription, + RunE: networkCreate, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman network create podman1`, } ) @@ -27,17 +29,36 @@ var ( networkCreateOptions entities.NetworkCreateOptions ) -func networkCreateFlags(flags *pflag.FlagSet) { - flags.StringVarP(&networkCreateOptions.Driver, "driver", "d", "bridge", "driver to manage the network") - flags.IPVar(&networkCreateOptions.Gateway, "gateway", nil, "IPv4 or IPv6 gateway for the subnet") +func networkCreateFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + driverFlagName := "driver" + flags.StringVarP(&networkCreateOptions.Driver, driverFlagName, "d", "bridge", "driver to manage the network") + _ = cmd.RegisterFlagCompletionFunc(driverFlagName, common.AutocompleteNetworkDriver) + + gatewayFlagName := "gateway" + flags.IPVar(&networkCreateOptions.Gateway, gatewayFlagName, nil, "IPv4 or IPv6 gateway for the subnet") + _ = cmd.RegisterFlagCompletionFunc(gatewayFlagName, completion.AutocompleteNone) + flags.BoolVar(&networkCreateOptions.Internal, "internal", false, "restrict external access from this network") - flags.IPNetVar(&networkCreateOptions.Range, "ip-range", net.IPNet{}, "allocate container IP from range") - flags.StringVar(&networkCreateOptions.MacVLAN, "macvlan", "", "create a Macvlan connection based on this device") + + ipRangeFlagName := "ip-range" + flags.IPNetVar(&networkCreateOptions.Range, ipRangeFlagName, net.IPNet{}, "allocate container IP from range") + _ = cmd.RegisterFlagCompletionFunc(ipRangeFlagName, completion.AutocompleteNone) + + macvlanFlagName := "macvlan" + flags.StringVar(&networkCreateOptions.MacVLAN, macvlanFlagName, "", "create a Macvlan connection based on this device") + _ = cmd.RegisterFlagCompletionFunc(macvlanFlagName, completion.AutocompleteNone) + // TODO not supported yet // flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver") - // TODO enable when IPv6 is working - // flags.BoolVar(&networkCreateOptions.IPV6, "IPv6", false, "enable IPv6 networking") - flags.IPNetVar(&networkCreateOptions.Subnet, "subnet", net.IPNet{}, "subnet in CIDR format") + + flags.BoolVar(&networkCreateOptions.IPv6, "ipv6", false, "enable IPv6 networking") + + subnetFlagName := "subnet" + flags.IPNetVar(&networkCreateOptions.Subnet, subnetFlagName, net.IPNet{}, "subnet in CIDR format") + _ = cmd.RegisterFlagCompletionFunc(subnetFlagName, completion.AutocompleteNone) + flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin") } func init() { @@ -46,8 +67,7 @@ func init() { Command: networkCreateCommand, Parent: networkCmd, }) - flags := networkCreateCommand.Flags() - networkCreateFlags(flags) + networkCreateFlags(networkCreateCommand) } diff --git a/cmd/podman/networks/disconnect.go b/cmd/podman/networks/disconnect.go new file mode 100644 index 000000000..a30315774 --- /dev/null +++ b/cmd/podman/networks/disconnect.go @@ -0,0 +1,45 @@ +package network + +import ( + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networkDisconnectDescription = `Remove container from a network` + networkDisconnectCommand = &cobra.Command{ + Use: "disconnect [options] NETWORK CONTAINER", + Short: "network rm", + Long: networkDisconnectDescription, + RunE: networkDisconnect, + Example: `podman network disconnect web secondary`, + Args: cobra.ExactArgs(2), + ValidArgsFunction: common.AutocompleteNetworkConnectCmd, + } +) + +var ( + networkDisconnectOptions entities.NetworkDisconnectOptions +) + +func networkDisconnectFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&networkDisconnectOptions.Force, "force", "f", false, "force removal of container from network") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: networkDisconnectCommand, + Parent: networkCmd, + }) + flags := networkDisconnectCommand.Flags() + networkDisconnectFlags(flags) +} + +func networkDisconnect(cmd *cobra.Command, args []string) error { + networkDisconnectOptions.Container = args[1] + return registry.ContainerEngine().NetworkDisconnect(registry.Context(), args[0], networkDisconnectOptions) +} diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go index 25ee7e574..671b0265f 100644 --- a/cmd/podman/networks/inspect.go +++ b/cmd/podman/networks/inspect.go @@ -1,6 +1,7 @@ package network import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/inspect" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" @@ -10,12 +11,13 @@ import ( var ( networkinspectDescription = `Inspect network` networkinspectCommand = &cobra.Command{ - Use: "inspect [options] NETWORK [NETWORK...]", - Short: "network inspect", - Long: networkinspectDescription, - RunE: networkInspect, - Example: `podman network inspect podman`, - Args: cobra.MinimumNArgs(1), + Use: "inspect [options] NETWORK [NETWORK...]", + Short: "network inspect", + Long: networkinspectDescription, + RunE: networkInspect, + Example: `podman network inspect podman`, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: common.AutocompleteNetworks, } inspectOpts *entities.InspectOptions ) @@ -28,7 +30,10 @@ func init() { }) inspectOpts = new(entities.InspectOptions) flags := networkinspectCommand.Flags() - flags.StringVarP(&inspectOpts.Format, "format", "f", "", "Pretty-print network to JSON or using a Go template") + + formatFlagName := "format" + flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "", "Pretty-print network to JSON or using a Go template") + _ = networkinspectCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func networkInspect(_ *cobra.Command, args []string) error { diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index 532af631e..dcba3f186 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -8,6 +8,10 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/completion" + "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/network" @@ -19,27 +23,31 @@ import ( var ( networklistDescription = `List networks` networklistCommand = &cobra.Command{ - Use: "ls [options]", - Args: validate.NoArgs, - Short: "network list", - Long: networklistDescription, - RunE: networkList, - Example: `podman network list`, + Use: "ls [options]", + Args: validate.NoArgs, + Short: "network list", + Long: networklistDescription, + RunE: networkList, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman network list`, } ) var ( networkListOptions entities.NetworkListOptions - headers = "NAME\tVERSION\tPLUGINS\n" - defaultListRow = "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" ) func networkListFlags(flags *pflag.FlagSet) { - // TODO enable filters based on something - // flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers") - flags.StringVarP(&networkListOptions.Format, "format", "f", "", "Pretty-print networks to JSON or using a Go template") + formatFlagName := "format" + flags.StringVarP(&networkListOptions.Format, formatFlagName, "f", "", "Pretty-print networks to JSON or using a Go template") + _ = networklistCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names") - flags.StringVarP(&networkListOptions.Filter, "filter", "", "", "Provide filter values (e.g. 'name=podman')") + + filterFlagName := "filter" + flags.StringVarP(&networkListOptions.Filter, filterFlagName, "", "", "Provide filter values (e.g. 'name=podman')") + _ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteNetworkFilters) + } func init() { @@ -66,13 +74,12 @@ func networkList(cmd *cobra.Command, args []string) error { return err } - // quiet means we only print the network names - if networkListOptions.Quiet { - return quietOut(responses) - } - - if strings.ToLower(networkListOptions.Format) == "json" { + switch { + case report.IsJSON(networkListOptions.Format): return jsonOut(responses) + case networkListOptions.Quiet: + // quiet means we only print the network names + return quietOut(responses) } nlprs := make([]ListPrintReports, 0, len(responses)) @@ -80,27 +87,34 @@ func networkList(cmd *cobra.Command, args []string) error { nlprs = append(nlprs, ListPrintReports{r}) } - row := networkListOptions.Format - if len(row) < 1 { - row = defaultListRow - } - if !strings.HasSuffix(row, "\n") { - row += "\n" + // Headers() gets lost resolving the embedded field names so add them + headers := report.Headers(ListPrintReports{}, map[string]string{ + "Name": "name", + "CNIVersion": "version", + "Version": "version", + "Plugins": "plugins", + }) + renderHeaders := true + row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" + if cmd.Flags().Changed("format") { + renderHeaders = parse.HasTable(networkListOptions.Format) + row = report.NormalizeFormat(networkListOptions.Format) } + format := parse.EnforceRange(row) - format := "{{range . }}" + row + "{{end}}" - if !cmd.Flag("format").Changed { - format = headers + format - } tmpl, err := template.New("listNetworks").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - if err := tmpl.Execute(w, nlprs); err != nil { - return err + defer w.Flush() + + if renderHeaders { + if err := tmpl.Execute(w, headers); err != nil { + return err + } } - return w.Flush() + return tmpl.Execute(w, nlprs) } func quietOut(responses []*entities.NetworkListReport) error { diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go index 3d7db941a..1504d9385 100644 --- a/cmd/podman/networks/rm.go +++ b/cmd/podman/networks/rm.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/libpod/define" @@ -16,12 +17,14 @@ import ( var ( networkrmDescription = `Remove networks` networkrmCommand = &cobra.Command{ - Use: "rm [options] NETWORK [NETWORK...]", - Short: "network rm", - Long: networkrmDescription, - RunE: networkRm, - Example: `podman network rm podman`, - Args: cobra.MinimumNArgs(1), + Use: "rm [options] NETWORK [NETWORK...]", + Aliases: []string{"remove"}, + Short: "network rm", + Long: networkrmDescription, + RunE: networkRm, + Example: `podman network rm podman`, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: common.AutocompleteNetworks, } ) diff --git a/cmd/podman/parse/template.go b/cmd/podman/parse/template.go new file mode 100644 index 000000000..0b80f1b3a --- /dev/null +++ b/cmd/podman/parse/template.go @@ -0,0 +1,22 @@ +package parse + +import ( + "regexp" + "strings" +) + +var rangeRegex = regexp.MustCompile(`{{\s*range\s*\.\s*}}.*{{\s*end\s*}}`) + +// TODO move to github.com/containers/common/pkg/report +// EnforceRange ensures that the format string contains a range +func EnforceRange(format string) string { + if !rangeRegex.MatchString(format) { + return "{{range .}}" + format + "{{end}}" + } + return format +} + +// EnforceRange ensures that the format string contains a range +func HasTable(format string) bool { + return strings.HasPrefix(format, "table ") +} diff --git a/cmd/podman/parse/template_test.go b/cmd/podman/parse/template_test.go new file mode 100644 index 000000000..7880d9bec --- /dev/null +++ b/cmd/podman/parse/template_test.go @@ -0,0 +1,30 @@ +package parse + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnforceRange(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"{{range .}}{{.ID}}{{end}}", "{{range .}}{{.ID}}{{end}}"}, + {"{{.ID}}", "{{range .}}{{.ID}}{{end}}"}, + {"{{ range . }}{{ .ID }}{{ end }}", "{{ range . }}{{ .ID }}{{ end }}"}, + // EnforceRange does not verify syntax or semantics, that will happen later + {"{{range .}}{{.ID}}", "{{range .}}{{range .}}{{.ID}}{{end}}"}, + {".ID", "{{range .}}.ID{{end}}"}, + } + + for _, tc := range tests { + tc := tc + label := "TestEnforceRange_" + tc.input + t.Run(label, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.expected, EnforceRange(tc.input)) + }) + } +} diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 6072ea80c..db70ad7d4 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -5,12 +5,13 @@ import ( "os" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -21,6 +22,7 @@ type playKubeOptionsWrapper struct { TLSVerifyCLI bool CredentialsCLI string + StartCLI bool } var ( @@ -32,11 +34,12 @@ var ( It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.` kubeCmd = &cobra.Command{ - Use: "kube [options] KUBEFILE", - Short: "Play a pod based on Kubernetes YAML.", - Long: kubeDescription, - RunE: kube, - Args: cobra.ExactArgs(1), + Use: "kube [options] KUBEFILE", + Short: "Play a pod based on Kubernetes YAML.", + Long: kubeDescription, + RunE: kube, + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.AutocompleteDefault, Example: `podman play kube nginx.yml podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml`, } @@ -51,16 +54,42 @@ func init() { flags := kubeCmd.Flags() flags.SetNormalizeFunc(utils.AliasFlags) - flags.StringVar(&kubeOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - flags.StringVar(&kubeOptions.Network, "network", "", "Connect pod to CNI network(s)") + + credsFlagName := "creds" + flags.StringVar(&kubeOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = kubeCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + + networkFlagName := "network" + flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") + _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworks) + + logDriverFlagName := "log-driver" + flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container") + _ = kubeCmd.RegisterFlagCompletionFunc(logDriverFlagName, common.AutocompleteLogDriver) + flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - flags.StringVar(&kubeOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.BoolVar(&kubeOptions.StartCLI, "start", true, "Start the pod after creating it") + + authfileFlagName := "authfile" + flags.StringVar(&kubeOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = kubeCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + if !registry.IsRemote() { - flags.StringVar(&kubeOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + + certDirFlagName := "cert-dir" + flags.StringVar(&kubeOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") + _ = kubeCmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") - flags.StringSliceVar(&kubeOptions.ConfigMaps, "configmap", []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") + + seccompProfileRootFlagName := "seccomp-profile-root" + flags.StringVar(&kubeOptions.SeccompProfileRoot, seccompProfileRootFlagName, defaultSeccompRoot, "Directory path for seccomp profiles") + _ = kubeCmd.RegisterFlagCompletionFunc(seccompProfileRootFlagName, completion.AutocompleteDefault) + + configmapFlagName := "configmap" + flags.StringSliceVar(&kubeOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") + _ = kubeCmd.RegisterFlagCompletionFunc(configmapFlagName, completion.AutocompleteDefault) } _ = flags.MarkHidden("signature-policy") } @@ -73,9 +102,12 @@ func kube(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("tls-verify") { kubeOptions.SkipTLSVerify = types.NewOptionalBool(!kubeOptions.TLSVerifyCLI) } + if cmd.Flags().Changed("start") { + kubeOptions.Start = types.NewOptionalBool(kubeOptions.StartCLI) + } if kubeOptions.Authfile != "" { if _, err := os.Stat(kubeOptions.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", kubeOptions.Authfile) + return err } } if kubeOptions.CredentialsCLI != "" { diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index efa84dcb4..5b0aa2fe4 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" @@ -27,11 +28,12 @@ var ( You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'.` createCommand = &cobra.Command{ - Use: "create [options]", - Args: validate.NoArgs, - Short: "Create a new empty pod", - Long: podCreateDescription, - RunE: create, + Use: "create [options]", + Args: validate.NoArgs, + Short: "Create a new empty pod", + Long: podCreateDescription, + RunE: create, + ValidArgsFunction: completion.AutocompleteNone, } ) @@ -51,19 +53,53 @@ func init() { }) flags := createCommand.Flags() flags.SetInterspersed(false) - flags.AddFlagSet(common.GetNetFlags()) - flags.StringVar(&createOptions.CGroupParent, "cgroup-parent", "", "Set parent cgroup for the pod") + + common.DefineNetFlags(createCommand) + + cgroupParentflagName := "cgroup-parent" + flags.StringVar(&createOptions.CGroupParent, cgroupParentflagName, "", "Set parent cgroup for the pod") + _ = createCommand.RegisterFlagCompletionFunc(cgroupParentflagName, completion.AutocompleteDefault) + flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") - flags.StringVar(&createOptions.InfraConmonPidFile, "infra-conmon-pidfile", "", "Path to the file that will receive the POD of the infra container's conmon") - flags.String("infra-image", containerConfig.Engine.InfraImage, "The image of the infra container to associate with the pod") - flags.String("infra-command", containerConfig.Engine.InfraCommand, "The command to run on the infra container when the pod is started") - flags.StringSliceVar(&labelFile, "label-file", []string{}, "Read in a line delimited file of labels") - flags.StringSliceVarP(&labels, "label", "l", []string{}, "Set metadata on pod (default [])") - flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") - flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") - flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") - flags.BoolVar(&replace, "replace", false, "If a pod with the same exists, replace it") - flags.StringVar(&share, "share", specgen.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + + infraConmonPidfileFlagName := "infra-conmon-pidfile" + flags.StringVar(&createOptions.InfraConmonPidFile, infraConmonPidfileFlagName, "", "Path to the file that will receive the POD of the infra container's conmon") + _ = createCommand.RegisterFlagCompletionFunc(infraConmonPidfileFlagName, completion.AutocompleteDefault) + + infraImageFlagName := "infra-image" + flags.String(infraImageFlagName, containerConfig.Engine.InfraImage, "The image of the infra container to associate with the pod") + _ = createCommand.RegisterFlagCompletionFunc(infraImageFlagName, common.AutocompleteImages) + + infraCommandFlagName := "infra-command" + flags.String(infraCommandFlagName, containerConfig.Engine.InfraCommand, "The command to run on the infra container when the pod is started") + _ = createCommand.RegisterFlagCompletionFunc(infraCommandFlagName, completion.AutocompleteNone) + + labelFileFlagName := "label-file" + flags.StringSliceVar(&labelFile, labelFileFlagName, []string{}, "Read in a line delimited file of labels") + _ = createCommand.RegisterFlagCompletionFunc(labelFileFlagName, completion.AutocompleteDefault) + + labelFlagName := "label" + flags.StringSliceVarP(&labels, labelFlagName, "l", []string{}, "Set metadata on pod (default [])") + _ = createCommand.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone) + + nameFlagName := "name" + flags.StringVarP(&createOptions.Name, nameFlagName, "n", "", "Assign a name to the pod") + _ = createCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) + + hostnameFlagName := "hostname" + flags.StringVarP(&createOptions.Hostname, hostnameFlagName, "", "", "Set a hostname to the pod") + _ = createCommand.RegisterFlagCompletionFunc(hostnameFlagName, completion.AutocompleteNone) + + podIDFileFlagName := "pod-id-file" + flags.StringVar(&podIDFile, podIDFileFlagName, "", "Write the pod ID to the file") + _ = createCommand.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) + + flags.BoolVar(&replace, "replace", false, "If a pod with the same name exists, replace it") + + shareFlagName := "share" + flags.StringVar(&share, shareFlagName, specgen.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + _ = createCommand.RegisterFlagCompletionFunc(shareFlagName, common.AutocompletePodShareNamespace) + flags.SetNormalizeFunc(aliasNetworkFlag) } @@ -135,33 +171,7 @@ func create(cmd *cobra.Command, args []string) error { if err != nil { return err } - createOptions.Net.Network = specgen.Namespace{} - if cmd.Flag("network").Changed { - netInput, err := cmd.Flags().GetString("network") - if err != nil { - return err - } - parts := strings.SplitN(netInput, ":", 2) - - n := specgen.Namespace{} - switch { - case netInput == "bridge": - n.NSMode = specgen.Bridge - case netInput == "host": - n.NSMode = specgen.Host - case netInput == "slirp4netns", strings.HasPrefix(netInput, "slirp4netns:"): - n.NSMode = specgen.Slirp - if len(parts) > 1 { - createOptions.Net.NetworkOptions = make(map[string][]string) - createOptions.Net.NetworkOptions[parts[0]] = strings.Split(parts[1], ",") - } - default: - // Container and NS mode are presently unsupported - n.NSMode = specgen.Bridge - createOptions.Net.CNINetworks = strings.Split(netInput, ",") - } - createOptions.Net.Network = n - } + if len(createOptions.Net.PublishPorts) > 0 { if !createOptions.Infra { return errors.Errorf("you must have an infra container to publish port bindings to the host") @@ -182,7 +192,7 @@ func create(cmd *cobra.Command, args []string) error { } if len(podIDFile) > 0 { if err = ioutil.WriteFile(podIDFile, []byte(response.Id), 0644); err != nil { - return errors.Wrapf(err, "failed to write pod ID to file %q", podIDFile) + return errors.Wrapf(err, "failed to write pod ID to file") } } fmt.Println(response.Id) diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go index cdaf2a707..a5c45803e 100644 --- a/cmd/podman/pods/exists.go +++ b/cmd/podman/pods/exists.go @@ -3,6 +3,7 @@ package pods import ( "context" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -12,11 +13,12 @@ var ( podExistsDescription = `If the named pod exists in local storage, podman pod exists exits with 0, otherwise the exit code will be 1.` existsCommand = &cobra.Command{ - Use: "exists POD", - Short: "Check if a pod exists in local storage", - Long: podExistsDescription, - RunE: exists, - Args: cobra.ExactArgs(1), + Use: "exists POD", + Short: "Check if a pod exists in local storage", + Long: podExistsDescription, + RunE: exists, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompletePods, Example: `podman pod exists podID podman pod exists mypod || podman pod create --name mypod`, DisableFlagsInUseLine: true, diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go index 7f81ba8fb..091094ff6 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -8,6 +8,7 @@ import ( "text/template" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -25,11 +26,12 @@ var ( By default, this will render all results in a JSON array.`) inspectCmd = &cobra.Command{ - Use: "inspect [options] POD [POD...]", - Short: "Displays a pod configuration", - Long: inspectDescription, - RunE: inspect, - Example: `podman pod inspect podID`, + Use: "inspect [options] POD [POD...]", + Short: "Displays a pod configuration", + Long: inspectDescription, + RunE: inspect, + ValidArgsFunction: common.AutocompletePods, + Example: `podman pod inspect podID`, } ) @@ -40,7 +42,11 @@ func init() { Parent: podCmd, }) flags := inspectCmd.Flags() - flags.StringVarP(&inspectOptions.Format, "format", "f", "json", "Format the output to a Go template or json") + + formatFlagName := "format" + flags.StringVarP(&inspectOptions.Format, formatFlagName, "f", "json", "Format the output to a Go template or json") + _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + validate.AddLatestFlag(inspectCmd, &inspectOptions.Latest) } diff --git a/cmd/podman/pods/kill.go b/cmd/podman/pods/kill.go index 1902a2c80..be8fd31df 100644 --- a/cmd/podman/pods/kill.go +++ b/cmd/podman/pods/kill.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -23,6 +24,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod kill podID podman pod kill --signal TERM mywebserver podman pod kill --latest`, @@ -41,7 +43,11 @@ func init() { }) flags := killCommand.Flags() flags.BoolVarP(&killOpts.All, "all", "a", false, "Kill all containers in all pods") - flags.StringVarP(&killOpts.Signal, "signal", "s", "KILL", "Signal to send to the containers in the pod") + + signalFlagName := "signal" + flags.StringVarP(&killOpts.Signal, signalFlagName, "s", "KILL", "Signal to send to the containers in the pod") + _ = killCommand.RegisterFlagCompletionFunc(signalFlagName, common.AutocompleteStopSignal) + validate.AddLatestFlag(killCommand, &killOpts.Latest) } diff --git a/cmd/podman/pods/pause.go b/cmd/podman/pods/pause.go index bba26f90d..108893173 100644 --- a/cmd/podman/pods/pause.go +++ b/cmd/podman/pods/pause.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -23,6 +24,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod pause podID1 podID2 podman pod pause --latest podman pod pause --all`, diff --git a/cmd/podman/pods/prune.go b/cmd/podman/pods/prune.go index e3eae3f71..444b0f5e0 100644 --- a/cmd/podman/pods/prune.go +++ b/cmd/podman/pods/prune.go @@ -7,11 +7,11 @@ import ( "os" "strings" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -23,12 +23,13 @@ var ( pruneDescription = fmt.Sprintf(`podman pod prune Removes all exited pods`) pruneCommand = &cobra.Command{ - Use: "prune [options]", - Args: validate.NoArgs, - Short: "Remove all stopped pods and their containers", - Long: pruneDescription, - RunE: prune, - Example: `podman pod prune`, + Use: "prune [options]", + Args: validate.NoArgs, + Short: "Remove all stopped pods and their containers", + Long: pruneDescription, + RunE: prune, + ValidArgsFunction: common.AutocompletePods, + Example: `podman pod prune`, } ) @@ -49,7 +50,7 @@ func prune(cmd *cobra.Command, args []string) error { fmt.Print("Are you sure you want to continue? [y/N] ") answer, err := reader.ReadString('\n') if err != nil { - return errors.Wrapf(err, "error reading input") + return err } if strings.ToLower(answer)[0] != 'y' { return nil diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 688108c1a..99d324411 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -10,7 +10,10 @@ import ( "text/template" "time" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -24,12 +27,13 @@ var ( // Command: podman pod _ps_ psCmd = &cobra.Command{ - Use: "ps [options]", - Aliases: []string{"ls", "list"}, - Short: "List pods", - Long: psDescription, - RunE: pods, - Args: validate.NoArgs, + Use: "ps [options]", + Aliases: []string{"ls", "list"}, + Short: "List pods", + Long: psDescription, + RunE: pods, + Args: validate.NoArgs, + ValidArgsFunction: completion.AutocompleteNone, } ) @@ -50,13 +54,24 @@ func init() { flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status") // TODO should we make this a [] ? - flags.StringSliceVarP(&inputFilters, "filter", "f", []string{}, "Filter output based on conditions given") - flags.StringVar(&psInput.Format, "format", "", "Pretty-print pods to JSON or using a Go template") + + filterFlagName := "filter" + flags.StringSliceVarP(&inputFilters, filterFlagName, "f", []string{}, "Filter output based on conditions given") + _ = psCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePodPsFilters) + + formatFlagName := "format" + flags.StringVar(&psInput.Format, formatFlagName, "", "Pretty-print pods to JSON or using a Go template") + _ = psCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod") flags.BoolVar(&psInput.Namespace, "ns", false, "Display namespace information of the pod") flags.BoolVar(&noTrunc, "no-trunc", false, "Do not truncate pod and container IDs") flags.BoolVarP(&psInput.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") - flags.StringVar(&psInput.Sort, "sort", "created", "Sort output by created, id, name, or number") + + sortFlagName := "sort" + flags.StringVar(&psInput.Sort, sortFlagName, "created", "Sort output by created, id, name, or number") + _ = psCmd.RegisterFlagCompletionFunc(sortFlagName, common.AutocompletePodPsSort) + validate.AddLatestFlag(psCmd, &psInput.Latest) } @@ -113,20 +128,22 @@ func pods(cmd *cobra.Command, _ []string) error { "Created": "CREATED", "InfraID": "INFRA ID", }) + renderHeaders := true row := podPsFormat() if cmd.Flags().Changed("format") { + renderHeaders = parse.HasTable(psInput.Format) row = report.NormalizeFormat(psInput.Format) } - row = "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("listPods").Parse(row) + tmpl, err := template.New("listPods").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if !psInput.Quiet && !cmd.Flag("format").Changed { + if renderHeaders { if err := tmpl.Execute(w, headers); err != nil { return err } diff --git a/cmd/podman/pods/restart.go b/cmd/podman/pods/restart.go index 119b4ddee..7a4b28a45 100644 --- a/cmd/podman/pods/restart.go +++ b/cmd/podman/pods/restart.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -23,6 +24,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + ValidArgsFunction: common.AutocompletePods, Example: `podman pod restart podID1 podID2 podman pod restart --latest podman pod restart --all`, diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index 714e075e2..ff238aa20 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" @@ -35,6 +36,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) }, + ValidArgsFunction: common.AutocompletePods, Example: `podman pod rm mywebserverpod podman pod rm -f 860a4b23 podman pod rm -f -a`, @@ -52,7 +54,11 @@ func init() { flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all running pods") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.StringArrayVarP(&rmOptions.PodIDFiles, "pod-id-file", "", nil, "Read the pod ID from the file") + + podIDFileFlagName := "pod-id-file" + flags.StringArrayVarP(&rmOptions.PodIDFiles, podIDFileFlagName, "", nil, "Read the pod ID from the file") + _ = rmCommand.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) + validate.AddLatestFlag(rmCommand, &rmOptions.Latest) if registry.IsRemote() { diff --git a/cmd/podman/pods/start.go b/cmd/podman/pods/start.go index 28ee4769a..7cd5c64d9 100644 --- a/cmd/podman/pods/start.go +++ b/cmd/podman/pods/start.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" @@ -31,6 +32,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) }, + ValidArgsFunction: common.AutocompletePods, Example: `podman pod start podID podman pod start --latest podman pod start --all`, @@ -50,7 +52,11 @@ func init() { flags := startCommand.Flags() flags.BoolVarP(&startOptions.All, "all", "a", false, "Restart all running pods") - flags.StringArrayVarP(&startOptions.PodIDFiles, "pod-id-file", "", nil, "Read the pod ID from the file") + + podIDFileFlagName := "pod-id-file" + flags.StringArrayVarP(&startOptions.PodIDFiles, podIDFileFlagName, "", nil, "Read the pod ID from the file") + _ = startCommand.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) + validate.AddLatestFlag(startCommand, &startOptions.Latest) } diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 338f13d3e..79e7cd8ed 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -10,6 +10,8 @@ import ( "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -32,10 +34,11 @@ var ( statsDescription = `Display the containers' resource-usage statistics of one or more running pod` // Command: podman pod _pod_ statsCmd = &cobra.Command{ - Use: "stats [options] [POD...]", - Short: "Display a live stream of resource usage statistics for the containers in one or more pods", - Long: statsDescription, - RunE: stats, + Use: "stats [options] [POD...]", + Short: "Display a live stream of resource usage statistics for the containers in one or more pods", + Long: statsDescription, + RunE: stats, + ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod stats podman pod stats a69b23034235 named-pod podman pod stats --latest @@ -52,7 +55,11 @@ func init() { flags := statsCmd.Flags() flags.BoolVarP(&statsOptions.All, "all", "a", false, "Provide stats for all pods") - flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + + formatFlagName := "format" + flags.StringVar(&statsOptions.Format, formatFlagName, "", "Pretty-print container statistics to JSON or using a Go template") + _ = statsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen when streaming") flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result") validate.AddLatestFlag(statsCmd, &statsOptions.Latest) @@ -135,7 +142,7 @@ func printFormattedPodStatsLines(headerNames []map[string]string, row string, st return nil } - row = "{{range .}}" + row + "{{end}}" + row = parse.EnforceRange(row) tmpl, err := template.New("pod stats").Parse(row) if err != nil { diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go index a2a9b0b57..d03364028 100644 --- a/cmd/podman/pods/stop.go +++ b/cmd/podman/pods/stop.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" @@ -36,6 +37,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) }, + ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod stop mywebserverpod podman pod stop --latest podman pod stop --time 0 490eb 3557fb`, @@ -51,8 +53,15 @@ func init() { flags := stopCommand.Flags() flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running pods") flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.UintVarP(&stopOptions.TimeoutCLI, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container") - flags.StringArrayVarP(&stopOptions.PodIDFiles, "pod-id-file", "", nil, "Read the pod ID from the file") + + timeFlagName := "time" + flags.UintVarP(&stopOptions.TimeoutCLI, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container") + _ = stopCommand.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) + + podIDFileFlagName := "pod-id-file" + flags.StringArrayVarP(&stopOptions.PodIDFiles, podIDFileFlagName, "", nil, "Write the pod ID to the file") + _ = stopCommand.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) + validate.AddLatestFlag(stopCommand, &stopOptions.Latest) if registry.IsRemote() { diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index 0ffa724da..829882080 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -7,6 +7,7 @@ import ( "strings" "text/tabwriter" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -23,11 +24,12 @@ var ( topOptions = entities.PodTopOptions{} topCommand = &cobra.Command{ - Use: "top [options] POD [FORMAT-DESCRIPTORS|ARGS...]", - Short: "Display the running processes of containers in a pod", - Long: topDescription, - RunE: top, - Args: cobra.ArbitraryArgs, + Use: "top [options] POD [FORMAT-DESCRIPTORS|ARGS...]", + Short: "Display the running processes of containers in a pod", + Long: topDescription, + RunE: top, + Args: cobra.ArbitraryArgs, + ValidArgsFunction: common.AutocompleteTopCmd, Example: `podman pod top podID podman pod top --latest podman pod top podID pid seccomp args %C diff --git a/cmd/podman/pods/unpause.go b/cmd/podman/pods/unpause.go index 15b30db14..499faec37 100644 --- a/cmd/podman/pods/unpause.go +++ b/cmd/podman/pods/unpause.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -23,6 +24,9 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + // TODO have a function which shows only pods which could be unpaused + // for now show all + ValidArgsFunction: common.AutocompletePods, Example: `podman pod unpause podID1 podID2 podman pod unpause --all podman pod unpause --latest`, diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go index 1e439613c..9c0b290e7 100644 --- a/cmd/podman/registry/registry.go +++ b/cmd/podman/registry/registry.go @@ -12,8 +12,11 @@ import ( "github.com/spf13/cobra" ) -// DefaultRootAPIAddress is the default address of the REST socket -const DefaultRootAPIAddress = "unix:/run/podman/podman.sock" +// DefaultRootAPIPath is the default path of the REST socket +const DefaultRootAPIPath = "/run/podman/podman.sock" + +// DefaultRootAPIAddress is the default address of the REST socket with unix: prefix +const DefaultRootAPIAddress = "unix:" + DefaultRootAPIPath // DefaultVarlinkAddress is the default address of the varlink socket const DefaultVarlinkAddress = "unix:/run/podman/io.podman" diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go index 78b820269..6e9b1fdfc 100644 --- a/cmd/podman/registry/remote.go +++ b/cmd/podman/registry/remote.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -23,7 +24,15 @@ func IsRemote() bool { fs.Usage = func() {} fs.SetInterspersed(false) fs.BoolVarP(&remoteFromCLI.Value, "remote", "r", false, "") - _ = fs.Parse(os.Args[1:]) + + // The shell completion logic will call a command called "__complete" or "__completeNoDesc" + // This command will always be the second argument + // To still parse --remote correctly in this case we have to set args offset to two in this case + start := 1 + if len(os.Args) > 1 && (os.Args[1] == cobra.ShellCompRequestCmd || os.Args[1] == cobra.ShellCompNoDescRequestCmd) { + start = 2 + } + _ = fs.Parse(os.Args[start:]) }) return podmanOptions.EngineMode == entities.TunnelMode || remoteFromCLI.Value } diff --git a/cmd/podman/root.go b/cmd/podman/root.go index b59b8341a..7840e6100 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -8,7 +8,9 @@ import ( "runtime/pprof" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -21,6 +23,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // HelpTemplate is the help template for podman commands @@ -67,9 +70,10 @@ var ( Version: version.Version.String(), DisableFlagsInUseLine: true, } - logLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"} - logLevel = "error" - useSyslog bool + + logLevel = "error" + useSyslog bool + requireCleanup = true ) func init() { @@ -102,15 +106,16 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { // TODO: Remove trace statement in podman V2.1 logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) - // Help and commands with subcommands are special cases, no need for more setup - if cmd.Name() == "help" || cmd.HasSubCommands() { + // Help, completion and commands with subcommands are special cases, no need for more setup + // Completion cmd is used to generate the shell scripts + if cmd.Name() == "help" || cmd.Name() == "completion" || cmd.HasSubCommands() { + requireCleanup = false return nil } cfg := registry.PodmanConfig() // --connection is not as "special" as --remote so we can wait and process it here - var connErr error conn := cmd.Root().LocalFlags().Lookup("connection") if conn != nil && conn.Changed { cfg.Engine.ActiveService = conn.Value.String() @@ -118,19 +123,37 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { var err error cfg.URI, cfg.Identity, err = cfg.ActiveDestination() if err != nil { - connErr = errors.Wrap(err, "failed to resolve active destination") + return errors.Wrap(err, "failed to resolve active destination") } if err := cmd.Root().LocalFlags().Set("url", cfg.URI); err != nil { - connErr = errors.Wrap(err, "failed to override --url flag") + return errors.Wrap(err, "failed to override --url flag") } if err := cmd.Root().LocalFlags().Set("identity", cfg.Identity); err != nil { - connErr = errors.Wrap(err, "failed to override --identity flag") + return errors.Wrap(err, "failed to override --identity flag") } } - if connErr != nil { - return connErr + + // Special case if command is hidden completion command ("__complete","__completeNoDesc") + // Since __completeNoDesc is an alias the cm.Name is always __complete + if cmd.Name() == cobra.ShellCompRequestCmd { + // Parse the cli arguments after the the completion cmd (always called as second argument) + // This ensures that the --url, --identity and --connection flags are properly set + compCmd, _, err := cmd.Root().Traverse(os.Args[2:]) + if err != nil { + return err + } + // If we don't complete the root cmd hide all root flags + // so they won't show up in the completions on subcommands. + if compCmd != compCmd.Root() { + compCmd.Root().Flags().VisitAll(func(flag *pflag.Flag) { + flag.Hidden = true + }) + } + // No need for further setup the completion logic setups the engines as needed. + requireCleanup = false + return nil } // Prep the engines @@ -160,8 +183,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { if cmd.Flag("cpu-profile").Changed { f, err := os.Create(cfg.CPUProfile) if err != nil { - return errors.Wrapf(err, "unable to create cpu profiling file %s", - cfg.CPUProfile) + return err } if err := pprof.StartCPUProfile(f); err != nil { return err @@ -203,8 +225,7 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error { // TODO: Remove trace statement in podman V2.1 logrus.Debugf("Called %s.PersistentPostRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) - // Help and commands with subcommands are special cases, no need for more cleanup - if cmd.Name() == "help" || cmd.HasSubCommands() { + if !requireCleanup { return nil } @@ -226,14 +247,14 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error { func loggingHook() { var found bool - for _, l := range logLevels { + for _, l := range common.LogLevels { if l == strings.ToLower(logLevel) { found = true break } } if !found { - fmt.Fprintf(os.Stderr, "Log Level %q is not supported, choose from: %s\n", logLevel, strings.Join(logLevels, ", ")) + fmt.Fprintf(os.Stderr, "Log Level %q is not supported, choose from: %s\n", logLevel, strings.Join(common.LogLevels, ", ")) os.Exit(1) } @@ -254,9 +275,18 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { srv, uri, ident := resolveDestination() lFlags := cmd.Flags() - lFlags.StringVarP(&opts.Engine.ActiveService, "connection", "c", srv, "Connection to use for remote Podman service") - lFlags.StringVar(&opts.URI, "url", uri, "URL to access Podman service (CONTAINER_HOST)") - lFlags.StringVar(&opts.Identity, "identity", ident, "path to SSH identity file, (CONTAINER_SSHKEY)") + + connectionFlagName := "connection" + lFlags.StringVarP(&opts.Engine.ActiveService, connectionFlagName, "c", srv, "Connection to use for remote Podman service") + _ = cmd.RegisterFlagCompletionFunc(connectionFlagName, common.AutocompleteSystemConnections) + + urlFlagName := "url" + lFlags.StringVar(&opts.URI, urlFlagName, uri, "URL to access Podman service (CONTAINER_HOST)") + _ = cmd.RegisterFlagCompletionFunc(urlFlagName, completion.AutocompleteDefault) + + identityFlagName := "identity" + lFlags.StringVar(&opts.Identity, identityFlagName, ident, "path to SSH identity file, (CONTAINER_SSHKEY)") + _ = cmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault) lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") pFlags := cmd.PersistentFlags() @@ -266,25 +296,67 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { } opts.Remote = true } else { - pFlags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") + cgroupManagerFlagName := "cgroup-manager" + pFlags.StringVar(&cfg.Engine.CgroupManager, cgroupManagerFlagName, cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") + _ = cmd.RegisterFlagCompletionFunc(cgroupManagerFlagName, common.AutocompleteCgroupManager) + pFlags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu profiling results") - pFlags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") - pFlags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") - pFlags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") + + conmonFlagName := "conmon" + pFlags.StringVar(&opts.ConmonPath, conmonFlagName, "", "Path of the conmon binary") + _ = cmd.RegisterFlagCompletionFunc(conmonFlagName, completion.AutocompleteDefault) + + networkCmdPathFlagName := "network-cmd-path" + pFlags.StringVar(&cfg.Engine.NetworkCmdPath, networkCmdPathFlagName, cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") + _ = cmd.RegisterFlagCompletionFunc(networkCmdPathFlagName, completion.AutocompleteDefault) + + cniConfigDirFlagName := "cni-config-dir" + pFlags.StringVar(&cfg.Network.NetworkConfigDir, cniConfigDirFlagName, cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") + _ = cmd.RegisterFlagCompletionFunc(cniConfigDirFlagName, completion.AutocompleteDefault) + pFlags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") - pFlags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) - pFlags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") + + eventsBackendFlagName := "events-backend" + pFlags.StringVar(&cfg.Engine.EventsLogger, eventsBackendFlagName, cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) + _ = cmd.RegisterFlagCompletionFunc(eventsBackendFlagName, common.AutocompleteEventBackend) + + hooksDirFlagName := "hooks-dir" + pFlags.StringSliceVar(&cfg.Engine.HooksDir, hooksDirFlagName, cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") + _ = cmd.RegisterFlagCompletionFunc(hooksDirFlagName, completion.AutocompleteDefault) + pFlags.IntVar(&opts.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations") - pFlags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") - pFlags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") + + namespaceFlagName := "namespace" + pFlags.StringVar(&cfg.Engine.Namespace, namespaceFlagName, cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") + _ = cmd.RegisterFlagCompletionFunc(namespaceFlagName, completion.AutocompleteNone) + + rootFlagName := "root" + pFlags.StringVar(&cfg.Engine.StaticDir, rootFlagName, "", "Path to the root directory in which data, including images, is stored") + _ = cmd.RegisterFlagCompletionFunc(rootFlagName, completion.AutocompleteDefault) + pFlags.StringVar(&opts.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing") - pFlags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") - pFlags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + + runrootFlagName := "runroot" + pFlags.StringVar(&opts.Runroot, runrootFlagName, "", "Path to the 'run directory' where all state information is stored") + _ = cmd.RegisterFlagCompletionFunc(runrootFlagName, completion.AutocompleteDefault) + + runtimeFlagName := "runtime" + pFlags.StringVar(&opts.RuntimePath, runtimeFlagName, "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + _ = cmd.RegisterFlagCompletionFunc(runtimeFlagName, completion.AutocompleteDefault) + // -s is deprecated due to conflict with -s on subcommands - pFlags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") - pFlags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + storageDriverFlagName := "storage-driver" + pFlags.StringVar(&opts.StorageDriver, storageDriverFlagName, "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + _ = cmd.RegisterFlagCompletionFunc(storageDriverFlagName, completion.AutocompleteNone) //TODO: what can we recommend here? + + storageOptFlagName := "storage-opt" + pFlags.StringArrayVar(&opts.StorageOpts, storageOptFlagName, []string{}, "Used to pass an option to the storage driver") + _ = cmd.RegisterFlagCompletionFunc(storageOptFlagName, completion.AutocompleteNone) + + tmpdirFlagName := "tmpdir" + pFlags.StringVar(&opts.Engine.TmpDir, tmpdirFlagName, "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") + _ = cmd.RegisterFlagCompletionFunc(tmpdirFlagName, completion.AutocompleteDefault) - pFlags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") pFlags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") // Hide these flags for both ABI and Tunneling @@ -303,11 +375,17 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { // Override default --help information of `--help` global flag var dummyHelp bool pFlags.BoolVar(&dummyHelp, "help", false, "Help for podman") - pFlags.StringVar(&logLevel, "log-level", logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(logLevels, ", "))) + + logLevelFlagName := "log-level" + pFlags.StringVar(&logLevel, logLevelFlagName, logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(common.LogLevels, ", "))) + _ = rootCmd.RegisterFlagCompletionFunc(logLevelFlagName, common.AutocompleteLogLevel) // Only create these flags for ABI connections if !registry.IsRemote() { - pFlags.StringArrayVar(&opts.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") + runtimeflagFlagName := "runtime-flag" + pFlags.StringArrayVar(&opts.RuntimeFlags, runtimeflagFlagName, []string{}, "add global flags for the container runtime") + _ = rootCmd.RegisterFlagCompletionFunc(runtimeflagFlagName, completion.AutocompleteNone) + pFlags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") } } diff --git a/cmd/podman/shell_completion_test.go b/cmd/podman/shell_completion_test.go new file mode 100644 index 000000000..d2b500b09 --- /dev/null +++ b/cmd/podman/shell_completion_test.go @@ -0,0 +1,71 @@ +/* + The purpose of this test is to keep a consistent + and great shell autocompletion experience. + + This test ensures that each command and flag has a shell completion + function set. (except boolean, hidden and deprecated flags) + + Shell completion functions are defined in: + - "github.com/containers/podman/v2/cmd/podman/common/completion.go" + - "github.com/containers/common/pkg/completion" + and are called Autocomplete... + + To apply such function to a command use the ValidArgsFunction field. + To apply such function to a flag use cmd.RegisterFlagCompletionFunc(name,func) + + If there are any questions/problems please tag Luap99. +*/ + +package main + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func TestShellCompletionFunctions(t *testing.T) { + + rootCmd := parseCommands() + checkCommand(t, rootCmd) + +} + +func checkCommand(t *testing.T, cmd *cobra.Command) { + + if cmd.HasSubCommands() { + for _, childCmd := range cmd.Commands() { + checkCommand(t, childCmd) + } + + // if not check if completion for that command is provided + } else if cmd.ValidArgsFunction == nil && cmd.ValidArgs == nil { + t.Errorf("%s command has no shell completion function set", cmd.CommandPath()) + } + + // loop over all local flags + cmd.LocalFlags().VisitAll(func(flag *pflag.Flag) { + + // an error means that there is a completion function for this flag + err := cmd.RegisterFlagCompletionFunc(flag.Name, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveDefault + }) + + switch { + case flag.Value.Type() == "bool" && err != nil: + // make sure bool flags don't have a completion function + t.Errorf(`%s --%s is a bool flag but has a shell completion function set. +You have to remove this shell completion function.`, cmd.CommandPath(), flag.Name) + return + + case flag.Value.Type() == "bool" || flag.Hidden || len(flag.Deprecated) > 0: + // skip bool, hidden and deprecated flags + return + + case err == nil: + // there is no shell completion function + t.Errorf("%s --%s flag has no shell completion function set", cmd.CommandPath(), flag.Name) + } + }) +} diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 0d81a64ca..57e747451 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -10,6 +10,7 @@ import ( "os/user" "regexp" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/system" @@ -34,7 +35,8 @@ var ( "destination" is of the form [user@]hostname or an URI of the form ssh://[user@]hostname[:port] `, - RunE: add, + RunE: add, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman system connection add laptop server.fubar.com podman system connection add --identity ~/.ssh/dev_rsa testing ssh://root@server.fubar.com:2222 podman system connection add --identity ~/.ssh/dev_rsa --port 22 production root@server.fubar.com @@ -57,9 +59,19 @@ func init() { }) flags := addCmd.Flags() - flags.IntVarP(&cOpts.Port, "port", "p", 22, "SSH port number for destination") - flags.StringVar(&cOpts.Identity, "identity", "", "path to SSH identity file") - flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") + + portFlagName := "port" + flags.IntVarP(&cOpts.Port, portFlagName, "p", 22, "SSH port number for destination") + _ = addCmd.RegisterFlagCompletionFunc(portFlagName, completion.AutocompleteNone) + + identityFlagName := "identity" + flags.StringVar(&cOpts.Identity, identityFlagName, "", "path to SSH identity file") + _ = addCmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault) + + socketPathFlagName := "socket-path" + flags.StringVar(&cOpts.UDSPath, socketPathFlagName, "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") + _ = addCmd.RegisterFlagCompletionFunc(socketPathFlagName, completion.AutocompleteDefault) + flags.BoolVarP(&cOpts.Default, "default", "d", false, "Set connection to be default") } @@ -67,14 +79,14 @@ func add(cmd *cobra.Command, args []string) error { // Default to ssh: schema if none given dest := args[1] if match, err := regexp.Match(schemaPattern, []byte(dest)); err != nil { - return errors.Wrapf(err, "internal regex error %q", schemaPattern) + return errors.Wrapf(err, "invalid destination") } else if !match { dest = "ssh://" + dest } uri, err := url.Parse(dest) if err != nil { - return errors.Wrapf(err, "failed to parse %q", dest) + return err } if uri.User.Username() == "" { @@ -97,7 +109,7 @@ func add(cmd *cobra.Command, args []string) error { if uri.Path == "" || uri.Path == "/" { if uri.Path, err = getUDS(cmd, uri); err != nil { - return errors.Wrapf(err, "failed to connect to %q", uri.String()) + return err } } @@ -139,7 +151,7 @@ func getUserInfo(uri *url.URL) (*url.Userinfo, error) { if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { usr, err = user.LookupId(u) if err != nil { - return nil, errors.Wrapf(err, "failed to find user %q", u) + return nil, errors.Wrapf(err, "failed to lookup rootless user") } } else { usr, err = user.Current() @@ -197,7 +209,7 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { } dial, err := ssh.Dial("tcp", uri.Host, cfg) if err != nil { - return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) + return "", errors.Wrapf(err, "failed to connect") } defer dial.Close() @@ -217,7 +229,7 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { var buffer bytes.Buffer session.Stdout = &buffer if err := session.Run(run); err != nil { - return "", errors.Wrapf(err, "failed to run %q", run) + return "", err } var info define.Info @@ -226,7 +238,7 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { } if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { - return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host) + return "", errors.Errorf("remote podman %q failed to report its UDS socket", uri.Host) } return info.Host.RemoteSocket.Path, nil } diff --git a/cmd/podman/system/connection/default.go b/cmd/podman/system/connection/default.go index eafcf37b2..e2ae6ae7f 100644 --- a/cmd/podman/system/connection/default.go +++ b/cmd/podman/system/connection/default.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/system" "github.com/containers/podman/v2/pkg/domain/entities" @@ -18,6 +19,7 @@ var ( Short: "Set named destination as default", Long: `Set named destination as default for the Podman service`, DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteSystemConnections, RunE: defaultRunE, Example: `podman system connection default testing`, } diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go index 9010ec803..b434559b4 100644 --- a/cmd/podman/system/connection/list.go +++ b/cmd/podman/system/connection/list.go @@ -5,6 +5,7 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/system" @@ -23,8 +24,9 @@ var ( DisableFlagsInUseLine: true, Example: `podman system connection list podman system connection ls`, - RunE: list, - TraverseChildren: false, + ValidArgsFunction: completion.AutocompleteNone, + RunE: list, + TraverseChildren: false, } ) diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go index 58674efb6..429325f50 100644 --- a/cmd/podman/system/connection/remove.go +++ b/cmd/podman/system/connection/remove.go @@ -2,6 +2,7 @@ package connection import ( "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/system" "github.com/containers/podman/v2/pkg/domain/entities" @@ -17,6 +18,7 @@ var ( Long: `Delete named destination from podman configuration`, Short: "Delete named destination", DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteSystemConnections, RunE: rm, Example: `podman system connection remove devl podman system connection rm devl`, diff --git a/cmd/podman/system/connection/rename.go b/cmd/podman/system/connection/rename.go index bb2ca262a..7ab94d49a 100644 --- a/cmd/podman/system/connection/rename.go +++ b/cmd/podman/system/connection/rename.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/system" "github.com/containers/podman/v2/pkg/domain/entities" @@ -19,6 +20,7 @@ var ( Short: "Rename \"old\" to \"new\"", Long: `Rename destination for the Podman service from "old" to "new"`, DisableFlagsInUseLine: true, + ValidArgsFunction: common.AutocompleteSystemConnections, RunE: rename, Example: `podman system connection rename laptop devl, podman system connection mv laptop devl`, diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go index b11167938..a9eab24bb 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -8,7 +8,9 @@ import ( "text/template" "time" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -23,11 +25,12 @@ var ( Show podman disk usage ` dfSystemCommand = &cobra.Command{ - Use: "df [options]", - Args: validate.NoArgs, - Short: "Show podman disk usage", - Long: dfSystemDescription, - RunE: df, + Use: "df [options]", + Args: validate.NoArgs, + Short: "Show podman disk usage", + Long: dfSystemDescription, + RunE: df, + ValidArgsFunction: completion.AutocompleteNone, } ) @@ -43,7 +46,11 @@ func init() { }) flags := dfSystemCommand.Flags() flags.BoolVarP(&dfOptions.Verbose, "verbose", "v", false, "Show detailed information on disk usage") - flags.StringVar(&dfOptions.Format, "format", "", "Pretty-print images using a Go template") + + formatFlagName := "format" + flags.StringVar(&dfOptions.Format, formatFlagName, "", "Pretty-print images using a Go template") + _ = dfSystemCommand.RegisterFlagCompletionFunc(formatFlagName, completion.AutocompleteNone) + } func df(cmd *cobra.Command, args []string) error { @@ -55,7 +62,7 @@ func df(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) if dfOptions.Verbose { - return printVerbose(cmd, w, reports) + return printVerbose(w, cmd, reports) } return printSummary(w, cmd, reports) } @@ -131,20 +138,16 @@ func printSummary(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.Sys "Size": "SIZE", "Reclaimable": "RECLAIMABLE", }) - row := "{{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}\n" if cmd.Flags().Changed("format") { row = report.NormalizeFormat(dfOptions.Format) } - row = "{{range . }}" + row + "{{end}}" - - return writeTemplate(cmd, w, hdrs, row, dfSummaries) + return writeTemplate(w, cmd, hdrs, row, dfSummaries) } -func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.SystemDfReport) error { +func printVerbose(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { defer w.Flush() - // Images fmt.Fprint(w, "Images space usage:\n\n") // convert to dfImage for output dfImages := make([]*dfImage, 0, len(reports.Images)) @@ -157,14 +160,11 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "UniqueSize": "UNIQUE SIZE", }) imageRow := "{{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}\n" - format := "{{range . }}" + imageRow + "{{end}}" - if err := writeTemplate(cmd, w, hdrs, format, dfImages); err != nil { + if err := writeTemplate(w, cmd, hdrs, imageRow, dfImages); err != nil { return nil } - // Containers fmt.Fprint(w, "\nContainers space usage:\n\n") - // convert to dfContainers for output dfContainers := make([]*dfContainer, 0, len(reports.Containers)) for _, d := range reports.Containers { @@ -176,14 +176,11 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "RWSize": "SIZE", }) containerRow := "{{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.RWSize}}\t{{.Created}}\t{{.Status}}\t{{.Names}}\n" - format = "{{range . }}" + containerRow + "{{end}}" - if err := writeTemplate(cmd, w, hdrs, format, dfContainers); err != nil { + if err := writeTemplate(w, cmd, hdrs, containerRow, dfContainers); err != nil { return nil } - // Volumes fmt.Fprint(w, "\nLocal Volumes space usage:\n\n") - dfVolumes := make([]*dfVolume, 0, len(reports.Volumes)) // convert to dfVolume for output for _, d := range reports.Volumes { @@ -193,14 +190,13 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "VolumeName": "VOLUME NAME", }) volumeRow := "{{.VolumeName}}\t{{.Links}}\t{{.Size}}\n" - format = "{{range . }}" + volumeRow + "{{end}}" - return writeTemplate(cmd, w, hdrs, format, dfVolumes) + return writeTemplate(w, cmd, hdrs, volumeRow, dfVolumes) } -func writeTemplate(cmd *cobra.Command, w *tabwriter.Writer, hdrs []map[string]string, format string, - output interface{}) error { +func writeTemplate(w *tabwriter.Writer, cmd *cobra.Command, hdrs []map[string]string, format string, output interface{}) error { defer w.Flush() + format = parse.EnforceRange(format) tmpl, err := template.New("df").Parse(format) if err != nil { return err diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 368cd41a6..d2aefab67 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -6,12 +6,13 @@ import ( "os" "text/template" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -20,11 +21,12 @@ var ( By default, streaming mode is used, printing new events as they occur. Previous events can be listed via --since and --until.` eventsCommand = &cobra.Command{ - Use: "events [options]", - Args: validate.NoArgs, - Short: "Show podman events", - Long: eventsDescription, - RunE: eventsCmd, + Use: "events [options]", + Args: validate.NoArgs, + Short: "Show podman events", + Long: eventsDescription, + RunE: eventsCmd, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman events podman events --filter event=create podman events --format {{.Image}} @@ -43,11 +45,25 @@ func init() { Command: eventsCommand, }) flags := eventsCommand.Flags() - flags.StringArrayVar(&eventOptions.Filter, "filter", []string{}, "filter output") - flags.StringVar(&eventFormat, "format", "", "format the output using a Go template") + + filterFlagName := "filter" + flags.StringArrayVar(&eventOptions.Filter, filterFlagName, []string{}, "filter output") + _ = eventsCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteEventFilter) + + formatFlagName := "format" + flags.StringVar(&eventFormat, formatFlagName, "", "format the output using a Go template") + _ = eventsCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&eventOptions.Stream, "stream", true, "stream new events; for testing only") - flags.StringVar(&eventOptions.Since, "since", "", "show all events created since timestamp") - flags.StringVar(&eventOptions.Until, "until", "", "show all events until timestamp") + + sinceFlagName := "since" + flags.StringVar(&eventOptions.Since, sinceFlagName, "", "show all events created since timestamp") + _ = eventsCommand.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone) + + untilFlagName := "until" + flags.StringVar(&eventOptions.Until, untilFlagName, "", "show all events until timestamp") + _ = eventsCommand.RegisterFlagCompletionFunc(untilFlagName, completion.AutocompleteNone) + _ = flags.MarkHidden("stream") } @@ -87,7 +103,7 @@ func eventsCmd(cmd *cobra.Command, _ []string) error { case doJSON: jsonStr, err := event.ToJSONString() if err != nil { - return errors.Wrapf(err, "unable to format json") + return err } fmt.Println(jsonStr) case cmd.Flags().Changed("format"): diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index dece6b37e..17aeb2940 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -5,13 +5,14 @@ import ( "os" "text/template" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/ghodss/yaml" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) var ( @@ -20,21 +21,23 @@ var ( Useful for the user and when reporting issues. ` infoCommand = &cobra.Command{ - Use: "info [options]", - Args: validate.NoArgs, - Long: infoDescription, - Short: "Display podman system information", - RunE: info, - Example: `podman info`, + Use: "info [options]", + Args: validate.NoArgs, + Long: infoDescription, + Short: "Display podman system information", + RunE: info, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman info`, } systemInfoCommand = &cobra.Command{ - Args: infoCommand.Args, - Use: infoCommand.Use, - Short: infoCommand.Short, - Long: infoCommand.Long, - RunE: infoCommand.RunE, - Example: `podman system info`, + Args: infoCommand.Args, + Use: infoCommand.Use, + Short: infoCommand.Short, + Long: infoCommand.Long, + RunE: infoCommand.RunE, + ValidArgsFunction: infoCommand.ValidArgsFunction, + Example: `podman system info`, } ) @@ -48,19 +51,24 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: infoCommand, }) - infoFlags(infoCommand.Flags()) + infoFlags(infoCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: systemInfoCommand, Parent: systemCmd, }) - infoFlags(systemInfoCommand.Flags()) + infoFlags(systemInfoCommand) } -func infoFlags(flags *pflag.FlagSet) { +func infoFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&debug, "debug", "D", false, "Display additional debug information") - flags.StringVarP(&inFormat, "format", "f", "", "Change the output format to JSON or a Go template") + + formatFlagName := "format" + flags.StringVarP(&inFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func info(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/system/migrate.go b/cmd/podman/system/migrate.go index 7870df60b..234a49e4b 100644 --- a/cmd/podman/system/migrate.go +++ b/cmd/podman/system/migrate.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -21,11 +22,12 @@ var ( ` migrateCommand = &cobra.Command{ - Use: "migrate [options]", - Args: validate.NoArgs, - Short: "Migrate containers", - Long: migrateDescription, - Run: migrate, + Use: "migrate [options]", + Args: validate.NoArgs, + Short: "Migrate containers", + Long: migrateDescription, + Run: migrate, + ValidArgsFunction: completion.AutocompleteNone, } ) @@ -41,7 +43,10 @@ func init() { }) flags := migrateCommand.Flags() - flags.StringVar(&migrateOptions.NewRuntime, "new-runtime", "", "Specify a new runtime for all containers") + + newRuntimeFlagName := "new-runtime" + flags.StringVar(&migrateOptions.NewRuntime, newRuntimeFlagName, "", "Specify a new runtime for all containers") + _ = migrateCommand.RegisterFlagCompletionFunc(newRuntimeFlagName, completion.AutocompleteNone) } func migrate(cmd *cobra.Command, args []string) { diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go index a229b06b0..f2b9a3db5 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -7,11 +7,11 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -25,12 +25,13 @@ var ( `) pruneCommand = &cobra.Command{ - Use: "prune [options]", - Short: "Remove unused data", - Args: validate.NoArgs, - Long: pruneDescription, - RunE: prune, - Example: `podman system prune`, + Use: "prune [options]", + Short: "Remove unused data", + Args: validate.NoArgs, + Long: pruneDescription, + RunE: prune, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman system prune`, } force bool ) @@ -66,7 +67,7 @@ WARNING! This will remove: Are you sure you want to continue? [y/N] `, volumeString) answer, err := reader.ReadString('\n') if err != nil { - return errors.Wrapf(err, "error reading input") + return err } if strings.ToLower(answer)[0] != 'y' { return nil diff --git a/cmd/podman/system/renumber.go b/cmd/podman/system/renumber.go index 1631ab093..b1683395f 100644 --- a/cmd/podman/system/renumber.go +++ b/cmd/podman/system/renumber.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -28,6 +29,7 @@ var ( Short: "Migrate lock numbers", Long: renumberDescription, Run: renumber, + ValidArgsFunction: completion.AutocompleteNone, } ) diff --git a/cmd/podman/system/reset.go b/cmd/podman/system/reset.go index 0b04c6ee0..97f4fba28 100644 --- a/cmd/podman/system/reset.go +++ b/cmd/podman/system/reset.go @@ -8,11 +8,12 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/infra" - "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -22,11 +23,12 @@ var ( All containers will be stopped and removed, and all images, volumes and container content will be removed. ` systemResetCommand = &cobra.Command{ - Use: "reset [options]", - Args: validate.NoArgs, - Short: "Reset podman storage", - Long: systemResetDescription, - Run: reset, + Use: "reset [options]", + Args: validate.NoArgs, + Short: "Reset podman storage", + Long: systemResetDescription, + Run: reset, + ValidArgsFunction: completion.AutocompleteNone, } forceFlag bool @@ -55,7 +57,7 @@ WARNING! This will remove: Are you sure you want to continue? [y/N] `) answer, err := reader.ReadString('\n') if err != nil { - fmt.Println(errors.Wrapf(err, "error reading input")) + logrus.Error(err) os.Exit(1) } if strings.ToLower(answer)[0] != 'y' { @@ -69,13 +71,13 @@ Are you sure you want to continue? [y/N] `) engine, err := infra.NewSystemEngine(entities.ResetMode, registry.PodmanConfig()) if err != nil { - fmt.Println(err) + logrus.Error(err) os.Exit(125) } defer engine.Shutdown(registry.Context()) if err := engine.Reset(registry.Context()); err != nil { - fmt.Println(err) + logrus.Error(err) os.Exit(125) } os.Exit(0) diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go index 2a2b1984f..78062d135 100644 --- a/cmd/podman/system/service.go +++ b/cmd/podman/system/service.go @@ -9,6 +9,7 @@ import ( "syscall" "time" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/rootless" @@ -26,12 +27,13 @@ Enable a listening service for API access to Podman commands. ` srvCmd = &cobra.Command{ - Use: "service [options] [URI]", - Args: cobra.MaximumNArgs(1), - Short: "Run API service", - Long: srvDescription, - RunE: service, - Example: `podman system service --time=0 unix:///tmp/podman.sock`, + Use: "service [options] [URI]", + Args: cobra.MaximumNArgs(1), + Short: "Run API service", + Long: srvDescription, + RunE: service, + ValidArgsFunction: completion.AutocompleteDefault, + Example: `podman system service --time=0 unix:///tmp/podman.sock`, } srvArgs = struct { @@ -48,7 +50,11 @@ func init() { }) flags := srvCmd.Flags() - flags.Int64VarP(&srvArgs.Timeout, "time", "t", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") + + timeFlagName := "time" + flags.Int64VarP(&srvArgs.Timeout, timeFlagName, "t", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") + _ = srvCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) + flags.BoolVar(&srvArgs.Varlink, "varlink", false, "Use legacy varlink service instead of REST. Unit of --time changes from seconds to milliseconds.") _ = flags.MarkDeprecated("varlink", "valink API is deprecated.") @@ -131,20 +137,17 @@ func resolveAPIURI(_url []string) (string, error) { if srvArgs.Varlink { socketName = "io.podman" } - socketDir := filepath.Join(xdg, "podman", socketName) - if _, err := os.Stat(filepath.Dir(socketDir)); err != nil { - if os.IsNotExist(err) { - if err := os.Mkdir(filepath.Dir(socketDir), 0755); err != nil { - return "", err - } - } else { - return "", err - } + socketPath := filepath.Join(xdg, "podman", socketName) + if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil { + return "", err } - return "unix:" + socketDir, nil + return "unix:" + socketPath, nil case srvArgs.Varlink: return registry.DefaultVarlinkAddress, nil default: + if err := os.MkdirAll(filepath.Dir(registry.DefaultRootAPIPath), 0700); err != nil { + return "", err + } return registry.DefaultRootAPIAddress, nil } } diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 95cbd19d9..8c52616be 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -5,12 +5,8 @@ package system import ( "context" "net" - "os" - "os/signal" "strings" - "github.com/containers/podman/v2/cmd/podman/utils" - "github.com/containers/podman/v2/libpod" api "github.com/containers/podman/v2/pkg/api/server" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/infra" @@ -33,7 +29,7 @@ func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entiti address := strings.Join(fields[1:], ":") l, err := net.Listen(fields[0], address) if err != nil { - return errors.Wrapf(err, "unable to create socket %s", opts.URI) + return errors.Wrapf(err, "unable to create socket") } listener = &l } @@ -43,7 +39,7 @@ func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entiti return err } - startWatcher(rt) + infra.StartWatcher(rt) server, err := api.NewServerWithSettings(rt, opts.Timeout, listener) if err != nil { return err @@ -60,24 +56,3 @@ func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entiti } return err } - -// startWatcher starts a new SIGHUP go routine for the current config. -func startWatcher(rt *libpod.Runtime) { - // Setup the signal notifier - ch := make(chan os.Signal, 1) - signal.Notify(ch, utils.SIGHUP) - - go func() { - for { - // Block until the signal is received - logrus.Debugf("waiting for SIGHUP to reload configuration") - <-ch - if err := rt.Reload(); err != nil { - logrus.Errorf("unable to reload configuration: %v", err) - continue - } - } - }() - - logrus.Debugf("registered SIGHUP watcher for config") -} diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 2d0113779..437cf7b2e 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -3,6 +3,7 @@ package system import ( "os" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/rootless" @@ -18,6 +19,7 @@ var ( Short: "Run a command in a modified user namespace", Long: unshareDescription, RunE: unshare, + ValidArgsFunction: completion.AutocompleteDefault, Example: `podman unshare id podman unshare cat /proc/self/uid_map, podman unshare podman-script.sh`, diff --git a/cmd/podman/system/varlink.go b/cmd/podman/system/varlink.go index 89669d51a..363ac9cca 100644 --- a/cmd/podman/system/varlink.go +++ b/cmd/podman/system/varlink.go @@ -5,6 +5,7 @@ package system import ( "time" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" @@ -16,13 +17,14 @@ var ( Tools speaking varlink protocol can remotely manage pods, containers and images. ` varlinkCmd = &cobra.Command{ - Use: "varlink [options] [URI]", - Args: cobra.MinimumNArgs(1), - Short: "Run varlink interface", - Long: varlinkDescription, - RunE: varlinkE, - Deprecated: "Please see 'podman system service' for RESTful APIs", - Hidden: true, + Use: "varlink [options] [URI]", + Args: cobra.MinimumNArgs(1), + Short: "Run varlink interface", + Long: varlinkDescription, + RunE: varlinkE, + ValidArgsFunction: completion.AutocompleteDefault, + Deprecated: "Please see 'podman system service' for RESTful APIs", + Hidden: true, Example: `podman varlink unix:/run/podman/io.podman podman varlink --time 5000 unix:/run/podman/io.podman`, } @@ -37,7 +39,11 @@ func init() { Command: varlinkCmd, }) flags := varlinkCmd.Flags() - flags.Int64VarP(&varlinkArgs.Timeout, "time", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + + timeFlagName := "time" + flags.Int64VarP(&varlinkArgs.Timeout, timeFlagName, "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + _ = varlinkCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) + flags.SetNormalizeFunc(aliasTimeoutFlag) } diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index b790a7511..cb7206b8a 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -8,7 +8,9 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -18,10 +20,11 @@ import ( var ( versionCommand = &cobra.Command{ - Use: "version [options]", - Args: validate.NoArgs, - Short: "Display the Podman Version Information", - RunE: version, + Use: "version [options]", + Args: validate.NoArgs, + Short: "Display the Podman Version Information", + RunE: version, + ValidArgsFunction: completion.AutocompleteNone, } versionFormat string ) @@ -32,7 +35,10 @@ func init() { Command: versionCommand, }) flags := versionCommand.Flags() - flags.StringVarP(&versionFormat, "format", "f", "", "Change the output format to JSON or a Go template") + + formatFlagName := "format" + flags.StringVarP(&versionFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template") + _ = versionCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func version(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/volumes/create.go b/cmd/podman/volumes/create.go index a54530183..8de343a24 100644 --- a/cmd/podman/volumes/create.go +++ b/cmd/podman/volumes/create.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" @@ -15,10 +16,11 @@ var ( createDescription = `If using the default driver, "local", the volume will be created on the host in the volumes directory under container storage.` createCommand = &cobra.Command{ - Use: "create [options] [NAME]", - Short: "Create a new volume", - Long: createDescription, - RunE: create, + Use: "create [options] [NAME]", + Short: "Create a new volume", + Long: createDescription, + RunE: create, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman volume create myvol podman volume create podman volume create --label foo=bar myvol`, @@ -40,9 +42,18 @@ func init() { Parent: volumeCmd, }) flags := createCommand.Flags() - flags.StringVar(&createOpts.Driver, "driver", "local", "Specify volume driver name") - flags.StringSliceVarP(&opts.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") - flags.StringArrayVarP(&opts.Opts, "opt", "o", []string{}, "Set driver specific options (default [])") + + driverFlagName := "driver" + flags.StringVar(&createOpts.Driver, driverFlagName, "local", "Specify volume driver name") + _ = createCommand.RegisterFlagCompletionFunc(driverFlagName, completion.AutocompleteNone) + + labelFlagName := "label" + flags.StringSliceVarP(&opts.Label, labelFlagName, "l", []string{}, "Set metadata for a volume (default [])") + _ = createCommand.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone) + + optFlagName := "opt" + flags.StringArrayVarP(&opts.Opts, optFlagName, "o", []string{}, "Set driver specific options (default [])") + _ = createCommand.RegisterFlagCompletionFunc(optFlagName, completion.AutocompleteNone) } func create(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go index c6edcf809..b5094224c 100644 --- a/cmd/podman/volumes/inspect.go +++ b/cmd/podman/volumes/inspect.go @@ -1,6 +1,7 @@ package volumes import ( + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/inspect" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" @@ -13,10 +14,11 @@ var ( Use a Go template to change the format from JSON.` inspectCommand = &cobra.Command{ - Use: "inspect [options] VOLUME [VOLUME...]", - Short: "Display detailed information on one or more volumes", - Long: volumeInspectDescription, - RunE: volumeInspect, + Use: "inspect [options] VOLUME [VOLUME...]", + Short: "Display detailed information on one or more volumes", + Long: volumeInspectDescription, + RunE: volumeInspect, + ValidArgsFunction: common.AutocompleteVolumes, Example: `podman volume inspect myvol podman volume inspect --all podman volume inspect --format "{{.Driver}} {{.Scope}}" myvol`, @@ -36,7 +38,10 @@ func init() { inspectOpts = new(entities.InspectOptions) flags := inspectCommand.Flags() flags.BoolVarP(&inspectOpts.All, "all", "a", false, "Inspect all volumes") - flags.StringVarP(&inspectOpts.Format, "format", "f", "json", "Format volume output using Go template") + + formatFlagName := "format" + flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "json", "Format volume output using Go template") + _ = inspectCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) } func volumeInspect(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index b3b2b8ea1..7e54de38a 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -8,7 +8,10 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -23,12 +26,13 @@ podman volume ls List all available volumes. The output of the volumes can be filtered and the output format can be changed to JSON or a user specified Go template.` lsCommand = &cobra.Command{ - Use: "ls [options]", - Aliases: []string{"list"}, - Args: validate.NoArgs, - Short: "List volumes", - Long: volumeLsDescription, - RunE: list, + Use: "ls [options]", + Aliases: []string{"list"}, + Args: validate.NoArgs, + Short: "List volumes", + Long: volumeLsDescription, + RunE: list, + ValidArgsFunction: completion.AutocompleteNone, } ) @@ -49,8 +53,15 @@ func init() { Parent: volumeCmd, }) flags := lsCommand.Flags() - flags.StringSliceVarP(&cliOpts.Filter, "filter", "f", []string{}, "Filter volume output") - flags.StringVar(&cliOpts.Format, "format", "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template") + + filterFlagName := "filter" + flags.StringSliceVarP(&cliOpts.Filter, filterFlagName, "f", []string{}, "Filter volume output") + _ = lsCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters) + + formatFlagName := "format" + flags.StringVar(&cliOpts.Format, formatFlagName, "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template") + _ = lsCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVarP(&cliOpts.Quiet, "quiet", "q", false, "Print volume output in quiet mode") } @@ -62,11 +73,11 @@ func list(cmd *cobra.Command, args []string) error { lsOpts.Filter = make(map[string][]string) } for _, f := range cliOpts.Filter { - filterSplit := strings.Split(f, "=") + filterSplit := strings.SplitN(f, "=", 2) if len(filterSplit) < 2 { return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } - lsOpts.Filter[filterSplit[0]] = append(lsOpts.Filter[filterSplit[0]], filterSplit[1:]...) + lsOpts.Filter[filterSplit[0]] = append(lsOpts.Filter[filterSplit[0]], filterSplit[1]) } responses, err := registry.ContainerEngine().VolumeList(context.Background(), lsOpts) if err != nil { @@ -91,9 +102,9 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) if cliOpts.Quiet { row = "{{.Name}}\n" } - row = "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("list volume").Parse(row) + tmpl, err := template.New("list volume").Parse(format) if err != nil { return err } diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index 79e6f1a54..4c2136dcf 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -7,11 +7,11 @@ import ( "os" "strings" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -21,11 +21,12 @@ var ( The command prompts for confirmation which can be overridden with the --force flag. Note all data will be destroyed.` pruneCommand = &cobra.Command{ - Use: "prune [options]", - Args: validate.NoArgs, - Short: "Remove all unused volumes", - Long: volumePruneDescription, - RunE: prune, + Use: "prune [options]", + Args: validate.NoArgs, + Short: "Remove all unused volumes", + Long: volumePruneDescription, + RunE: prune, + ValidArgsFunction: completion.AutocompleteNone, } ) @@ -51,7 +52,7 @@ func prune(cmd *cobra.Command, args []string) error { fmt.Print("Are you sure you want to continue? [y/N] ") answer, err := reader.ReadString('\n') if err != nil { - return errors.Wrapf(err, "error reading input") + return err } if strings.ToLower(answer)[0] != 'y' { return nil diff --git a/cmd/podman/volumes/rm.go b/cmd/podman/volumes/rm.go index 4026764ac..49f7b619e 100644 --- a/cmd/podman/volumes/rm.go +++ b/cmd/podman/volumes/rm.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/libpod/define" @@ -18,11 +19,12 @@ var ( By default only volumes that are not being used by any containers will be removed. To remove the volumes anyways, use the --force flag.` rmCommand = &cobra.Command{ - Use: "rm [options] VOLUME [VOLUME...]", - Aliases: []string{"remove"}, - Short: "Remove one or more volumes", - Long: volumeRmDescription, - RunE: rm, + Use: "rm [options] VOLUME [VOLUME...]", + Aliases: []string{"remove"}, + Short: "Remove one or more volumes", + Long: volumeRmDescription, + RunE: rm, + ValidArgsFunction: common.AutocompleteVolumes, Example: `podman volume rm myvol1 myvol2 podman volume rm --all podman volume rm --force myvol`, |