diff options
307 files changed, 18872 insertions, 6741 deletions
@@ -48,6 +48,7 @@ OCI_RUNTIME ?= "" BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions ZSHINSTALLDIR=${PREFIX}/share/zsh/site-functions +FISHINSTALLDIR=${PREFIX}/share/fish/vendor_completions.d SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) @@ -474,6 +475,15 @@ changelog: ## Generate changelog $(shell cat $(TMPFILE) >> changelog.txt) $(shell rm $(TMPFILE)) +completions: binaries + install ${SELINUXOPT} -d -m 755 completions/{bash,zsh,fish} + ./bin/podman completion bash --no-desc -f completions/bash/podman + ./bin/podman-remote completion bash --no-desc -f completions/bash/podman-remote + ./bin/podman completion zsh -f completions/zsh/_podman + ./bin/podman-remote completion zsh -f completions/zsh/_podman-remote + ./bin/podman completion fish -f completions/fish/podman.fish + ./bin/podman-remote completion fish -f completions/fish/podman-remote.fish + .PHONY: install install: .gopathok install.bin install.remote install.man install.cni install.systemd ## Install binaries to system locations @@ -512,8 +522,13 @@ install.man: docs install.man-nobuild install.completions: install ${SELINUXOPT} -d -m 755 ${DESTDIR}${BASHINSTALLDIR} install ${SELINUXOPT} -m 644 completions/bash/podman ${DESTDIR}${BASHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/bash/podman-remote ${DESTDIR}${BASHINSTALLDIR} install ${SELINUXOPT} -d -m 755 ${DESTDIR}${ZSHINSTALLDIR} install ${SELINUXOPT} -m 644 completions/zsh/_podman ${DESTDIR}${ZSHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/zsh/_podman-remote ${DESTDIR}${ZSHINSTALLDIR} + install ${SELINUXOPT} -d -m 755 ${DESTDIR}${FISHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/fish/podman.fish ${DESTDIR}${FISHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/fish/podman-remote.fish ${DESTDIR}${FISHINSTALLDIR} .PHONY: install.cni install.cni: @@ -656,9 +671,20 @@ API.md: pkg/varlink/io.podman.varlink $(GO) generate ./docs/... .PHONY: validate.completions -validate.completions: completions/bash/podman +validate.completions: SHELL:=/usr/bin/env bash # Set shell to bash for this target +validate.completions: + # Check that nobody has manually edited the completion scripts + # If this check fails run make completions to restore the correct scripts + diff completions/bash/podman <(./bin/podman completion --no-desc bash) + diff completions/zsh/_podman <(./bin/podman completion zsh) + diff completions/fish/podman.fish <(./bin/podman completion fish) + diff completions/bash/podman-remote <(./bin/podman-remote completion --no-desc bash) + diff completions/zsh/_podman-remote <(./bin/podman-remote completion zsh) + diff completions/fish/podman-remote.fish <(./bin/podman-remote completion fish) + # Check if the files can be loaded by the shell . completions/bash/podman if [ -x /bin/zsh ]; then /bin/zsh completions/zsh/_podman; fi + if [ -x /bin/fish ]; then /bin/fish completions/fish/podman.fish; fi .PHONY: validate validate: gofmt lint .gitvalidation validate.completions man-page-check swagger-check 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..4d66b4e2b --- /dev/null +++ b/cmd/podman/common/completion.go @@ -0,0 +1,638 @@ +package common + +import ( + "bufio" + "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" + systemdGen "github.com/containers/podman/v2/pkg/systemd/generate" + "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"} +) + +func getContainers(status string, toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + listOpts := entities.ContainerListOptions{ + Filters: make(map[string][]string), + } + listOpts.All = true + + if status != "all" { + listOpts.Filters = map[string][]string{"status": {status}} + } + + containers, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + + for _, container := range containers { + // include ids in suggestions if more then 2 chars are typed + if len(toComplete) > 1 { + suggestions = append(suggestions, container.ID[0:12]) + } + // include name in suggestions + suggestions = append(suggestions, container.Names...) + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getPods(status string, toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + listOpts := entities.PodPSOptions{ + Filters: make(map[string][]string), + } + + if status != "all" { + listOpts.Filters = map[string][]string{"status": {status}} + } + + pods, err := registry.ContainerEngine().PodPs(registry.GetContext(), listOpts) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + + for _, pod := range pods { + // include ids in suggestions if more then 2 chars are typed + if len(toComplete) > 1 { + suggestions = append(suggestions, pod.Id[0:12]) + } + // include name in suggestions + suggestions = append(suggestions, pod.Name) + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getVolumes() ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + lsOpts := entities.VolumeListOptions{} + + volumes, err := registry.ContainerEngine().VolumeList(registry.GetContext(), lsOpts) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + + for _, volume := range volumes { + suggestions = append(suggestions, volume.Name) + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getImages(toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + listOptions := entities.ImageListOptions{} + + images, err := registry.ImageEngine().List(registry.GetContext(), listOptions) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + + for _, image := range images { + // FIXME: need ux testing + // discuss when image ids should be completed + // include ids in suggestions if more then 2 chars are typed + if len(toComplete) > 1 { + suggestions = append(suggestions, image.ID[0:12]) + } + for _, repo := range image.RepoTags { + if toComplete == "" { + // suggest only full repo path if no input is given + suggestions = append(suggestions, repo) + } 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:], "/") + suggestionWithoutTag := strings.SplitN(strings.SplitN(suggestionWithTag, ":", 2)[0], "@", 2)[0] + suggestions = append(suggestions, suggestionWithTag, suggestionWithoutTag) + } + } + } + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func getRegistries() ([]string, cobra.ShellCompDirective) { + regs, err := registries.GetRegistries() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + return regs, cobra.ShellCompDirectiveNoFileComp +} + +func getNetworks() ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + networkListOptions := entities.NetworkListOptions{} + + networks, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + + for _, network := range networks { + suggestions = append(suggestions, network.Name) + } + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +/* Autocomplete Functions for cobra ValidArgsFunction */ + +// AutocompleteContainers - Autocomplete all container names. +func AutocompleteContainers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getContainers("all", toComplete) +} + +// AutocompleteContainersCreated - Autocomplete only created container names. +func AutocompleteContainersCreated(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getContainers("created", toComplete) +} + +// AutocompleteContainersExited - Autocomplete only exited container names. +func AutocompleteContainersExited(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getContainers("exited", toComplete) +} + +// AutocompleteContainersPaused - Autocomplete only paused container names. +func AutocompleteContainersPaused(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getContainers("paused", toComplete) +} + +// AutocompleteContainersRunning - Autocomplete only running container names. +func AutocompleteContainersRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getContainers("running", toComplete) +} + +// AutocompleteContainersStartable - Autocomplete only created and exited container names. +func AutocompleteContainersStartable(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + containersCreated, _ := getContainers("created", toComplete) + containersExited, _ := getContainers("exited", toComplete) + return append(containersCreated, containersExited...), cobra.ShellCompDirectiveNoFileComp +} + +// AutocompletePods - Autocomplete all pod names. +func AutocompletePods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getPods("all", toComplete) +} + +// AutocompletePodsRunning - Autocomplete only running pod names. +func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getPods("running", toComplete) +} + +// AutocompleteContainersAndPods - Autocomplete container names and pod names. +func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + containers, _ := getContainers("all", toComplete) + pods, _ := getPods("all", toComplete) + return append(containers, pods...), cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteContainersAndImages - Autocomplete container names and pod names. +func AutocompleteContainersAndImages(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + containers, _ := getContainers("all", toComplete) + images, _ := getImages(toComplete) + return append(containers, images...), cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteVolumes - Autocomplete volumes. +func AutocompleteVolumes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getVolumes() +} + +// AutocompleteImages - Autocomplete images. +func AutocompleteImages(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getImages(toComplete) +} + +// AutocompleteCreateRun - Autocomplete only the fist argument as image and then do file completion. +func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) < 1 { + return getImages(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) { + return getRegistries() +} + +// AutocompleteNetworks - Autocomplete networks. +func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getNetworks() +} + +// AutocompleteCpCommand - Autocomplete podman cp command args. +func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) < 2 { + containers, _ := getContainers("all", toComplete) + 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 +} + +// AutocompleteSystemConnections - Autocomplete system connections. +func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + suggestions := []string{} + cfg, err := config.ReadCustomConfig() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveError + } + + 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) { + namespacesOptions := []string{"host", "container:", "ns:", "private"} + + switch { + case strings.HasPrefix(toComplete, "container:"): + // Complete containers after colon + containers, _ := getContainers("all", toComplete[10:]) //trim "container:" + + // add "container:" in front of the suggestions + var suggestions []string + for _, container := range containers { + suggestions = append(suggestions, "container:"+container) + } + + return suggestions, cobra.ShellCompDirectiveNoFileComp + + case strings.HasPrefix(toComplete, "ns:"): + // Complete path after colon + return nil, cobra.ShellCompDirectiveDefault + + case strings.HasPrefix(toComplete, "c") || strings.HasPrefix(toComplete, "n"): + // don't insert space for container: and ns: + return []string{"container:", "ns:"}, cobra.ShellCompDirectiveNoSpace + } + return namespacesOptions, cobra.ShellCompDirectiveNoFileComp +} + +// 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) + if directive == cobra.ShellCompDirectiveNoFileComp { + // add the auto and keep-id options + 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) { + SecurityOptions := []string{"apparmor=", "no-new-privileges", "seccomp=", "label="} + switch { + case strings.HasPrefix(toComplete, "apparmor=u"): + // add space after unconfined + return []string{"apparmor=unconfined"}, cobra.ShellCompDirectiveNoFileComp + + case strings.HasPrefix(toComplete, "label=d"): + // add space after disable + return []string{"label=disable"}, cobra.ShellCompDirectiveNoFileComp + + case strings.HasPrefix(toComplete, "label="): + return []string{"label=user:", "label=role:", "label=type:", "label=level:", "label=filetype:", "label=disable"}, + cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace + + case strings.HasPrefix(toComplete, "seccomp="): + // complete files + return nil, cobra.ShellCompDirectiveDefault + + case strings.HasPrefix(toComplete, "n"): + // add space if no-new-privileges + return []string{"no-new-privileges"}, cobra.ShellCompDirectiveNoFileComp + } + return SecurityOptions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace +} + +// 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 { + return nil, cobra.ShellCompDirectiveError + } + 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 { + return nil, cobra.ShellCompDirectiveError + } + 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 { + return nil, cobra.ShellCompDirectiveError + } + 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.ShellCompDirectiveError + } + 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) { + result := []string{} + volumes, _ := getVolumes() + for _, volume := range volumes { + // If we don't filter on "toComplete", zsh and fish will not do file completion + // even if the prefix typed by the user does not match the returned completions + if strings.HasPrefix(volume, toComplete) { + result = append(result, volume) + } + } + directive := cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveDefault + if strings.Contains(toComplete, ":") { + // add space after second path + directive = cobra.ShellCompDirectiveDefault + } + return result, 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) { + filters := []string{"container=", "event=", "image=", "pod=", "volume=", "type="} + return filters, cobra.ShellCompDirectiveNoSpace +} + +// 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 +} diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 60f4e526c..ab3a984f0 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, containerConfig.Cgroups(), `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, containerConfig.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", containerConfig.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, containerConfig.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, containerConfig.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, containerConfig.Engine.PullPolicy, `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, containerConfig.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, containerConfig.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", containerConfig.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/netflags.go b/cmd/podman/common/netflags.go index 935a5f7b9..cae52ccaa 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -4,58 +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)", ) + _ = cmd.RegisterFlagCompletionFunc(macAddressFlagName, completion.AutocompleteNone) + + networkFlagName := "network" netFlags.String( - "network", containerConfig.NetNS(), + networkFlagName, containerConfig.NetNS(), "Connect a container to a network", ) + _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworks) + + networkAliasFlagName := "network-alias" netFlags.StringSlice( - "network-alias", []string{}, + 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) { 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 412dbf7a8..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 { 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..e8debd3ad 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 { diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 7da430bc6..a9e2d2e35 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -11,7 +11,9 @@ 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" @@ -21,17 +23,17 @@ 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]", + Args: validate.NoArgs, + Short: "List containers", + Long: psDescription, + RunE: ps, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman ps -a podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" podman ps --size --sort names`, @@ -50,26 +52,45 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: psCommand, }) - listFlagSet(psCommand.Flags()) + listFlagSet(psCommand) validate.AddLatestFlag(psCommand, &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") + //TODO add custom filter function + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + + 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 { 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 780cd0c0d..b17c13f46 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 { diff --git a/cmd/podman/containers/runlabel.go b/cmd/podman/containers/runlabel.go index 92581c26f..2f6d2eb05 100644 --- a/cmd/podman/containers/runlabel.go +++ b/cmd/podman/containers/runlabel.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/sirupsen/logrus" @@ -23,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`, @@ -42,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") diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 21f31d360..dba2c3c3e 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,27 @@ 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, + 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, + ValidArgsFunction: startCommand.ValidArgsFunction, Example: `podman container start --latest podman container start 860a4b231279 5421ab43b45 podman container start --interactive --attach imageID`, @@ -41,9 +43,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 +64,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,9 +72,8 @@ func init() { Command: containerStartCommand, Parent: containerCmd, }) - startFlags(containerStartCommand.Flags()) + startFlags(containerStartCommand) validate.AddLatestFlag(containerStartCommand, &startOptions.Latest) - } func start(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index bfab469ca..ca5c0fdb8 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -8,6 +8,7 @@ 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" @@ -20,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`, @@ -62,9 +64,15 @@ var ( 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) } 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..3eb6d2af2 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" @@ -26,11 +27,12 @@ var ( 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.AutocompleteContainersRunning, Example: `podman top ctrID podman top --latest podman top ctrID pid seccomp args %C @@ -38,10 +40,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 87f7501e3..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) } 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 5d0208c63..c76e4ac80 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. @@ -314,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 @@ -450,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 e9751b365..964c7a975 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -11,13 +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 ( @@ -27,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 { @@ -57,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") 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 e24631b24..4692699f2 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -10,15 +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 { @@ -35,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`, @@ -67,18 +69,32 @@ 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 [])") + // TODO: add completion function for filters + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + + 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") } diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index 02f1b3b39..a7884f4c5 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 an image from container 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..3af56b015 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -6,6 +6,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" @@ -19,12 +20,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 +44,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) } diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index ab3b0a197..a6f41688c 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.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/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // pullOptionsWrapper wraps entities.ImagePullOptions and prevents leaking @@ -29,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`, } @@ -42,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`, } @@ -58,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{ @@ -68,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") } diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index dd45a790f..447b02fbe 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -4,12 +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/spf13/cobra" - "github.com/spf13/pflag" ) // pushOptionsWrapper wraps entities.ImagepushOptions and prevents leaking @@ -28,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`, } @@ -41,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`, } @@ -57,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{ @@ -67,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() { 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 7de6a7316..c2ef7d767 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -7,6 +7,7 @@ import ( "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" @@ -14,7 +15,6 @@ import ( "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 @@ -34,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`, @@ -46,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`, @@ -64,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{ @@ -74,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 JSON or 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") } diff --git a/cmd/podman/images/sign.go b/cmd/podman/images/sign.go index f6c1f9856..529fb3d92 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 { 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..f0399b110 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 { 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..f9bd75c93 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -9,7 +9,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" @@ -44,8 +46,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 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..91bd423b8 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -5,7 +5,9 @@ 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" @@ -25,10 +27,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 +46,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") diff --git a/cmd/podman/manifest/annotate.go b/cmd/podman/manifest/annotate.go index 861e94034..dab8c4da6 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 { diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go index 956946f9d..c903c6fa8 100644 --- a/cmd/podman/manifest/create.go +++ b/cmd/podman/manifest/create.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" @@ -13,10 +14,11 @@ import ( 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`, diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go index 19396ceb3..17c94aaba 100644 --- a/cmd/podman/manifest/inspect.go +++ b/cmd/podman/manifest/inspect.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: "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, diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index 91881c1b3..9d0977834 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 quay.io/myimagelist`, + 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") 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/create.go b/cmd/podman/networks/create.go index c06011ce9..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,16 +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") + flags.BoolVar(&networkCreateOptions.IPv6, "ipv6", false, "enable IPv6 networking") - flags.IPNetVar(&networkCreateOptions.Subnet, "subnet", net.IPNet{}, "subnet in CIDR format") + + 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() { @@ -45,8 +67,7 @@ func init() { Command: networkCreateCommand, Parent: networkCmd, }) - flags := networkCreateCommand.Flags() - networkCreateFlags(flags) + networkCreateFlags(networkCreateCommand) } 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 f68e4ed75..bab6b45ea 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.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/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" @@ -21,12 +23,13 @@ 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`, } ) @@ -35,11 +38,16 @@ var ( ) 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, completion.AutocompleteNone) + } func init() { diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go index 3d7db941a..34e756a3a 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,13 @@ 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...]", + Short: "network rm", + Long: networkrmDescription, + RunE: networkRm, + Example: `podman network rm podman`, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: common.AutocompleteNetworks, } ) diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 4f34b2b76..a9e91bd68 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.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/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -31,11 +33,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`, } @@ -50,17 +53,41 @@ 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)") - flags.StringVar(&kubeOptions.LogDriver, "log-driver", "", "Logging driver for the container") + + 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") + + 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") } diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index efa84dcb4..d33455e81 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") + + 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 exists, replace it") - flags.StringVar(&share, "share", specgen.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + + 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) } 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..626ef2895 100644 --- a/cmd/podman/pods/prune.go +++ b/cmd/podman/pods/prune.go @@ -7,6 +7,7 @@ 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" @@ -23,12 +24,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`, } ) diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 40fc71780..51c2e92f0 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -10,7 +10,9 @@ 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" @@ -25,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, } ) @@ -51,13 +54,25 @@ 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") + //TODO complete filters + _ = psCmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + + 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) } 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 c5d1e7f07..79e7cd8ed 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -10,6 +10,7 @@ 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" @@ -33,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 @@ -53,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) 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..45ef1e7c2 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.AutocompletePodsRunning, 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/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..0a44f5eac 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,11 +106,36 @@ 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 } + // 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 when completing commands with subcommands. + if compCmd.HasSubCommands() { + requireCleanup = false + return nil + } + } + cfg := registry.PodmanConfig() // --connection is not as "special" as --remote so we can wait and process it here @@ -203,8 +232,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 +254,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 +282,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 +303,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 +382,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..b3a23bffd 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") } 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 fbdf274fb..a9eab24bb 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -8,6 +8,7 @@ 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" @@ -24,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, } ) @@ -44,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 { diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 368cd41a6..224ef89f3 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -6,7 +6,9 @@ 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" @@ -20,11 +22,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 +46,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") } 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..be0d60604 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -7,6 +7,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" @@ -25,12 +26,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 ) 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..d38a1a427 100644 --- a/cmd/podman/system/reset.go +++ b/cmd/podman/system/reset.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/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -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 diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go index 0476c632d..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.") 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 ce0b7997d..5548c9c1b 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.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/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" @@ -24,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, } ) @@ -50,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, completion.AutocompleteNone) + + 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") } diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index 79e6f1a54..2f58b668f 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -7,6 +7,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,11 +22,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, } ) 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`, diff --git a/completions/Readme.md b/completions/Readme.md new file mode 100644 index 000000000..9a3eac480 --- /dev/null +++ b/completions/Readme.md @@ -0,0 +1,7 @@ +# Shell completion scripts + +Podman offers shell completion scripts for bash, zsh and fish. The completion scripts are available for both `podman` and `podman-remote`. + +The shell completion scripts are generated by `make completion`, do not edit these files directly. To install them you can run `sudo make install.completions`. + +For information about these sripts see [`man podman-completion`](../docs/source/markdown/podman-completion.1.md) diff --git a/completions/bash/podman b/completions/bash/podman index c08bb3352..17d1e86b7 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1,3601 +1,271 @@ -# -# This bash script was originally copied and converted from the upstream -# github.com:docker/docker project -# -: ${PROG:=$(basename ${BASH_SOURCE})} +# bash completion for podman -*- shell-script -*- - -__podman_previous_extglob_setting=$(shopt -p extglob) -shopt -s extglob - -__podman_q() { - podman ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@" -} - -# __podman_containers returns a list of containers. Additional options to -# `podman ps` may be specified in order to filter the list, e.g. -# `__podman_containers --filter status=running` -# By default, only names are returned. -# Set PODMAN_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs. -# An optional first option `--id|--name` may be used to limit the -# output to the IDs or names of matching items. This setting takes -# precedence over the environment setting. -__podman_containers() { - local format - if [ "$1" = "--id" ] ; then - format='{{.ID}}' - shift - elif [ "$1" = "--name" ] ; then - format='{{.Names}}' - shift - elif [ "${PODMAN_COMPLETION_SHOW_CONTAINER_IDS}" = yes ] ; then - format='{{.ID}} {{.Names}}' - else - format='{{.Names}}' - fi - __podman_q ps --format "$format" "$@" -} - -__podman_list_registries() { - sed -n -e '/registries.*=/ {s/.*\[\([^]]*\).*/\1/p;q}' /etc/containers/registries.conf | sed -e "s/[,']//g" -} - -# __podman_pods returns a list of pods. Additional options to -# `podman pod ps` may be specified in order to filter the list, e.g. -# `__podman_containers --filter status=running` -# By default, only names are returned. -# Set PODMAN_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs. -# An optional first option `--id|--name` may be used to limit the -# output to the IDs or names of matching items. This setting takes -# precedence over the environment setting. -__podman_pods() { - local format - if [ "$1" = "--id" ] ; then - format='{{.ID}}' - shift - elif [ "$1" = "--name" ] ; then - format='{{.Name}}' - shift - else - format='{{.Name}}' - fi - __podman_q pod ps --format "$format" "$@" -} - -# __podman_complete_containers applies completion of containers based on the current -# value of `$cur` or the value of the optional first option `--cur`, if given. -# Additional filters may be appended, see `__podman_containers`. -__podman_complete_containers() { - local current="$cur" - if [ "$1" = "--cur" ] ; then - current="$2" - shift 2 - fi - COMPREPLY=( $(compgen -W "$(__podman_containers "$@")" -- "$current") ) -} - -# __podman_complete_pods applies completion of pods based on the current -# value of `$cur` or the value of the optional first option `--cur`, if given. -# Additional filters may be appended, see `__podman_pods`. -__podman_complete_pods() { - local current="$cur" - if [ "$1" = "--cur" ] ; then - current="$2" - shift 2 - fi - COMPREPLY=( $(compgen -W "$(__podman_pods "$@")" -- "$current") ) -} - -__podman_complete_pod_names() { - local names=( $(__podman_q pod ps --format={{.Name}}) ) - COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") ) -} - -__podman_complete_containers_all() { - __podman_complete_containers "$@" --all -} - -__podman_complete_containers_created() { - __podman_complete_containers "$@" --all --filter status=created -} - -__podman_complete_containers_running() { - __podman_complete_containers "$@" --filter status=running -} - -__podman_complete_containers_stopped() { - __podman_complete_containers "$@" --all --filter status=exited -} - -__podman_complete_containers_unpauseable() { - __podman_complete_containers "$@" --all --filter status=paused -} - -__podman_complete_container_names() { - local containers=( $(__podman_q ps -aq --no-trunc) ) - local names=( $(__podman_q inspect --format '{{.Name}}' "${containers[@]}") ) - names=( "${names[@]#/}" ) # trim off the leading "/" from the container names - COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") ) -} - -__podman_complete_container_ids() { - local containers=( $(__podman_q ps -aq) ) - COMPREPLY=( $(compgen -W "${containers[*]}" -- "$cur") ) -} - -__podman_images() { - local images_args="" - - case "$PODMAN_COMPLETION_SHOW_IMAGE_IDS" in - all) - images_args="--no-trunc -a" - ;; - non-intermediate) - images_args="--no-trunc" - ;; - esac - - local repo_print_command - if [ "${PODMAN_COMPLETION_SHOW_TAGS:-yes}" = "yes" ]; then - repo_print_command='print $1; print $1":"$2' - else - repo_print_command='print $1' - fi - - local awk_script - case "$PODMAN_COMPLETION_SHOW_IMAGE_IDS" in - all|non-intermediate) - awk_script='NR>1 { print $3; if ($1 != "<none>") { '"$repo_print_command"' } }' - ;; - none|*) - awk_script='NR>1 && $1 != "<none>" { '"$repo_print_command"' }' - ;; - esac - - __podman_q images $images_args | awk "$awk_script" | grep -v '<none>$' -} - -__podman_complete_images() { - COMPREPLY=( $(compgen -W "$(__podman_images)" -- "$cur") ) - __ltrim_colon_completions "$cur" -} - -__podman_complete_image_repos() { - local repos="$(__podman_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')" - COMPREPLY=( $(compgen -W "$repos" -- "$cur") ) -} - -__podman_complete_image_repos_and_tags() { - local reposAndTags="$(__podman_q images | awk 'NR>1 && $1 != "<none>" { print $1; print $1":"$2 }')" - COMPREPLY=( $(compgen -W "$reposAndTags" -- "$cur") ) - __ltrim_colon_completions "$cur" -} - -# __podman_networks returns a list of all networks. Additional options to -# `podman network ls` may be specified in order to filter the list, e.g. -# `__podman_networks --filter type=custom` -# By default, only names are returned. -# Set PODMAN_COMPLETION_SHOW_NETWORK_IDS=yes to also complete IDs. -# An optional first option `--id|--name` may be used to limit the -# output to the IDs or names of matching items. This setting takes -# precedence over the environment setting. -__podman_networks() { - local format - if [ "$1" = "--id" ] ; then - format='{{.ID}}' - shift - elif [ "$1" = "--name" ] ; then - format='{{.Name}}' - shift - elif [ "${PODMAN_COMPLETION_SHOW_NETWORK_IDS}" = yes ] ; then - format='{{.ID}} {{.Name}}' - else - format='{{.Name}}' - fi - __podman_q network ls --format "$format" "$@" -} - -# __podman_complete_networks applies completion of networks based on the current -# value of `$cur` or the value of the optional first option `--cur`, if given. -# Additional filters may be appended, see `__podman_networks`. -__podman_complete_networks() { - local current="$cur" - if [ "$1" = "--cur" ] ; then - current="$2" - shift 2 - fi - COMPREPLY=( $(compgen -W "$(__podman_networks "$@")" -- "$current") ) -} - -__podman_complete_containers_in_network() { - local containers=$(__podman_q network inspect -f '{{range $i, $c := .Containers}}{{$i}} {{$c.Name}} {{end}}' "$1") - COMPREPLY=( $(compgen -W "$containers" -- "$cur") ) -} - -__podman_runtimes() { - __podman_q info | sed -n 's/^Runtimes: \(.*\)/\1/p' -} - -__podman_complete_runtimes() { - COMPREPLY=( $(compgen -W "$(__podman_runtimes)" -- "$cur") ) -} - -# __podman_services returns a list of all services. Additional options to -# `podman service ls` may be specified in order to filter the list, e.g. -# `__podman_services --filter name=xxx` -# By default, only node names are returned. -# Set PODMAN_COMPLETION_SHOW_SERVICE_IDS=yes to also complete IDs. -# An optional first option `--id|--name` may be used to limit the -# output to the IDs or names of matching items. This setting takes -# precedence over the environment setting. -__podman_services() { - local fields='$2' # default: service name only - [ "${PODMAN_COMPLETION_SHOW_SERVICE_IDS}" = yes ] && fields='$1,$2' # ID & name - - if [ "$1" = "--id" ] ; then - fields='$1' # IDs only - shift - elif [ "$1" = "--name" ] ; then - fields='$2' # names only - shift - fi - __podman_q service ls "$@" | awk "NR>1 {print $fields}" -} - -# __podman_complete_services applies completion of services based on the current -# value of `$cur` or the value of the optional first option `--cur`, if given. -# Additional filters may be appended, see `__podman_services`. -__podman_complete_services() { - local current="$cur" - if [ "$1" = "--cur" ] ; then - current="$2" - shift 2 - fi - COMPREPLY=( $(compgen -W "$(__podman_services "$@")" -- "$current") ) -} - -# __podman_append_to_completions appends the word passed as an argument to every -# word in `$COMPREPLY`. -# Normally you do this with `compgen -S` while generating the completions. -# This function allows you to append a suffix later. It allows you to use -# the __podman_complete_XXX functions in cases where you need a suffix. -__podman_append_to_completions() { - COMPREPLY=( ${COMPREPLY[@]/%/"$1"} ) -} - -# __podman_pos_first_nonflag finds the position of the first word that is neither -# option nor an option's argument. If there are options that require arguments, -# you should pass a glob describing those options, e.g. "--option1|-o|--option2" -# Use this function to restrict completions to exact positions after the argument list. -__podman_pos_first_nonflag() { - local argument_flags=$1 - - local counter=$((${subcommand_pos:-${command_pos}} + 1)) - while [ $counter -le $cword ]; do - if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then - (( counter++ )) - # eat "=" in case of --option=arg syntax - [ "${words[$counter]}" = "=" ] && (( counter++ )) - else - case "${words[$counter]}" in - -*) - ;; - *) - break - ;; - esac - fi - - # Bash splits words at "=", retaining "=" as a word, examples: - # "--debug=false" => 3 words, "--log-opt syslog-facility=daemon" => 4 words - while [ "${words[$counter + 1]}" = "=" ] ; do - counter=$(( counter + 2)) - done - - (( counter++ )) - done - - echo $counter -} - -# __podman_map_key_of_current_option returns `key` if we are currently completing the -# value of a map option (`key=value`) which matches the extglob given as an argument. -# This function is needed for key-specific completions. -__podman_map_key_of_current_option() { - local glob="$1" - - local key glob_pos - if [ "$cur" = "=" ] ; then # key= case - key="$prev" - glob_pos=$((cword - 2)) - elif [[ $cur == *=* ]] ; then # key=value case (OSX) - key=${cur%=*} - glob_pos=$((cword - 1)) - elif [ "$prev" = "=" ] ; then - key=${words[$cword - 2]} # key=value case - glob_pos=$((cword - 3)) - else - return - fi - - [ "${words[$glob_pos]}" = "=" ] && ((glob_pos--)) # --option=key=value syntax - - [[ ${words[$glob_pos]} == @($glob) ]] && echo "$key" -} - -# __podman_value_of_option returns the value of the first option matching `option_glob`. -# Valid values for `option_glob` are option names like `--log-level` and globs like -# `--log-level|-l` -# Only positions between the command and the current word are considered. -__podman_value_of_option() { - local option_extglob=$(__podman_to_extglob "$1") - - local counter=$((command_pos + 1)) - while [ $counter -lt $cword ]; do - case ${words[$counter]} in - $option_extglob ) - echo ${words[$counter + 1]} - break - ;; - esac - (( counter++ )) - done -} - -# __podman_to_alternatives transforms a multiline list of strings into a single line -# string with the words separated by `|`. -# This is used to prepare arguments to __podman_pos_first_nonflag(). -__podman_to_alternatives() { - local parts=( $1 ) - local IFS='|' - echo "${parts[*]}" -} - -# __podman_to_extglob transforms a multiline list of options into an extglob pattern -# suitable for use in case statements. -__podman_to_extglob() { - local extglob=$( __podman_to_alternatives "$1" ) - echo "@($extglob)" -} - -# __podman_subcommands processes subcommands -# Locates the first occurrence of any of the subcommands contained in the -# first argument. In case of a match, calls the corresponding completion -# function and returns 0. -# If no match is found, 1 is returned. The calling function can then -# continue processing its completion. -# -# TODO if the preceding command has options that accept arguments and an -# argument is equal to one of the subcommands, this is falsely detected as -# a match. -__podman_subcommands() { - local subcommands="$1" - - local counter=$(($command_pos + 1)) - - while [ $counter -lt $cword ]; do - case "${words[$counter]}" in - $(__podman_to_extglob "$subcommands") ) - subcommand_pos=$counter - local subcommand=${words[$counter]} - local completions_func=_podman_${command}_${subcommand} - declare -F $completions_func >/dev/null && $completions_func - return 0 - ;; - esac - (( counter++ )) - done - return 1 -} - -# __podman_nospace suppresses trailing whitespace -__podman_nospace() { - # compopt is not available in ancient bash versions - type compopt &>/dev/null && compopt -o nospace -} - -__podman_complete_resolved_hostname() { - command -v host >/dev/null 2>&1 || return - COMPREPLY=( $(host 2>/dev/null "${cur%:}" | awk '/has address/ {print $4}') ) -} - -__podman_local_interfaces() { - command -v ip >/dev/null 2>&1 || return - ip addr show scope global 2>/dev/null | sed -n 's| \+inet \([0-9.]\+\).* \([^ ]\+\)|\1 \2|p' -} - -__podman_complete_restart() { - case "$prev" in - --restart) - COMPREPLY=( $( compgen -W "always no on-failure unless-stopped" -- "$cur") ) - return - ;; - esac - return 1 -} - -__podman_complete_local_interfaces() { - local additional_interface - if [ "$1" = "--add" ] ; then - additional_interface="$2" - fi - - COMPREPLY=( $( compgen -W "$(__podman_local_interfaces) $additional_interface" -- "$cur" ) ) -} - -__podman_complete_capabilities() { - # The list of capabilities is defined in types.go, ALL was added manually. - COMPREPLY=( $( compgen -W " - ALL - AUDIT_CONTROL - AUDIT_WRITE - AUDIT_READ - BLOCK_SUSPEND - CHOWN - DAC_OVERRIDE - DAC_READ_SEARCH - FOWNER - FSETID - IPC_LOCK - IPC_OWNER - KILL - LEASE - LINUX_IMMUTABLE - MAC_ADMIN - MAC_OVERRIDE - MKNOD - NET_ADMIN - NET_BIND_SERVICE - NET_BROADCAST - NET_RAW - SETFCAP - SETGID - SETPCAP - SETUID - SYS_ADMIN - SYS_BOOT - SYS_CHROOT - SYSLOG - SYS_MODULE - SYS_NICE - SYS_PACCT - SYS_PTRACE - SYS_RAWIO - SYS_RESOURCE - SYS_TIME - SYS_TTY_CONFIG - WAKE_ALARM - " -- "$cur" ) ) -} - -__podman_complete_detach_keys() { - case "$prev" in - --detach-keys) - case "$cur" in - *,) - COMPREPLY=( $( compgen -W "${cur}ctrl-" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "ctrl-" -- "$cur" ) ) - ;; - esac - - __podman_nospace - return 0 - ;; - esac - return 1 -} - -__podman_complete_log_drivers() { - COMPREPLY=( $( compgen -W " - awslogs - etwlogs - fluentd - gcplogs - gelf - journald - json-file - logentries - none - splunk - syslog - k8s-file - " -- "$cur" ) ) -} - -__podman_complete_log_options() { - # see docs/reference/logging/index.md - local awslogs_options="awslogs-region awslogs-group awslogs-stream" - local fluentd_options="env fluentd-address fluentd-async-connect fluentd-buffer-limit fluentd-retry-wait fluentd-max-retries labels tag" - local gcplogs_options="env gcp-log-cmd gcp-project labels" - local gelf_options="env gelf-address gelf-compression-level gelf-compression-type labels tag" - local journald_options="env labels tag" - local json_file_options="env labels max-file max-size" - local logentries_options="logentries-token" - local syslog_options="env labels syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag" - local splunk_options="env labels splunk-caname splunk-capath splunk-format splunk-gzip splunk-gzip-level splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url splunk-verify-connection tag" - local k8s_file_options="env labels max-file max-size" - - local all_options="$fluentd_options $gcplogs_options $gelf_options $journald_options $logentries_options $json_file_options $syslog_options $splunk_options" - - case $(__podman_value_of_option --log-driver) in - '') - COMPREPLY=( $( compgen -W "$all_options" -S = -- "$cur" ) ) - ;; - awslogs) - COMPREPLY=( $( compgen -W "$awslogs_options" -S = -- "$cur" ) ) - ;; - fluentd) - COMPREPLY=( $( compgen -W "$fluentd_options" -S = -- "$cur" ) ) - ;; - gcplogs) - COMPREPLY=( $( compgen -W "$gcplogs_options" -S = -- "$cur" ) ) - ;; - gelf) - COMPREPLY=( $( compgen -W "$gelf_options" -S = -- "$cur" ) ) - ;; - journald) - COMPREPLY=( $( compgen -W "$journald_options" -S = -- "$cur" ) ) - ;; - json-file) - COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) ) - ;; - k8s-file) - COMPREPLY=( $( compgen -W "$k8s_file_options" -S = -- "$cur" ) ) - ;; - logentries) - COMPREPLY=( $( compgen -W "$logentries_options" -S = -- "$cur" ) ) - ;; - syslog) - COMPREPLY=( $( compgen -W "$syslog_options" -S = -- "$cur" ) ) - ;; - splunk) - COMPREPLY=( $( compgen -W "$splunk_options" -S = -- "$cur" ) ) - ;; - *) - return - ;; - esac - - __podman_nospace -} - -__podman_complete_log_driver_options() { - local key=$(__podman_map_key_of_current_option '--log-opt') - case "$key" in - fluentd-async-connect) - COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) ) - return - ;; - gelf-address) - COMPREPLY=( $( compgen -W "udp" -S "://" -- "${cur##*=}" ) ) - __podman_nospace - return - ;; - gelf-compression-level) - COMPREPLY=( $( compgen -W "1 2 3 4 5 6 7 8 9" -- "${cur##*=}" ) ) - return - ;; - gelf-compression-type) - COMPREPLY=( $( compgen -W "gzip none zlib" -- "${cur##*=}" ) ) - return - ;; - syslog-address) - COMPREPLY=( $( compgen -W "tcp:// tcp+tls:// udp:// unix://" -- "${cur##*=}" ) ) - __podman_nospace - __ltrim_colon_completions "${cur}" - return - ;; - syslog-facility) - COMPREPLY=( $( compgen -W " - auth - authpriv - cron - daemon - ftp - kern - local0 - local1 - local2 - local3 - local4 - local5 - local6 - local7 - lpr - mail - news - syslog - user - uucp - " -- "${cur##*=}" ) ) - return - ;; - syslog-format) - COMPREPLY=( $( compgen -W "rfc3164 rfc5424 rfc5424micro" -- "${cur##*=}" ) ) - return - ;; - syslog-tls-ca-cert|syslog-tls-cert|syslog-tls-key) - _filedir - return - ;; - syslog-tls-skip-verify) - COMPREPLY=( $( compgen -W "true" -- "${cur##*=}" ) ) - return - ;; - splunk-url) - COMPREPLY=( $( compgen -W "http:// https://" -- "${cur##*=}" ) ) - __podman_nospace - __ltrim_colon_completions "${cur}" - return - ;; - splunk-gzip|splunk-insecureskipverify|splunk-verify-connection) - COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) ) - return - ;; - splunk-format) - COMPREPLY=( $( compgen -W "inline json raw" -- "${cur##*=}" ) ) - return - ;; - esac - return 1 -} - -__podman_complete_log_levels() { - COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) -} - -# __podman_complete_signals returns a subset of the available signals that is most likely -# relevant in the context of podman containers -__podman_complete_signals() { - local signals=( - SIGCONT - SIGHUP - SIGINT - SIGKILL - SIGQUIT - SIGSTOP - SIGTERM - SIGUSR1 - SIGUSR2 - ) - COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) ) -} - -__podman_complete_user_group() { - if [[ $cur == *:* ]] ; then - COMPREPLY=( $(compgen -g -- "${cur#*:}") ) - else - COMPREPLY=( $(compgen -u -S : -- "$cur") ) - __podman_nospace - fi -} - -__podman_list_images() { - COMPREPLY=($(compgen -W "$(podman images -q)" -- $cur)) -} - -__podman_list_containers() { - COMPREPLY=($(compgen -W "$(podman ps -aq)" -- $cur)) -} - -__podman_images() { - local images_args="" - - case "$PODMAN_COMPLETION_SHOW_IMAGE_IDS" in - all) - images_args="--no-trunc -a" - ;; - non-intermediate) - images_args="--no-trunc" - ;; - esac - - local repo_print_command - if [ "${PODMAN_COMPLETION_SHOW_TAGS:-yes}" = "yes" ]; then - repo_print_command='print $1; print $1":"$2' - else - repo_print_command='print $1' - fi - - local awk_script - case "$PODMAN_COMPLETION_SHOW_IMAGE_IDS" in - all|non-intermediate) - awk_script='NR>1 { print $3; if ($1 != "<none>") { '"$repo_print_command"' } }' - ;; - none|*) - awk_script='NR>1 && $1 != "<none>" { '"$repo_print_command"' }' - ;; - esac - - __podman_q images $images_args | awk "$awk_script" | grep -v '<none>$' -} - -_podman_auto_update() { - local options_with_args=" - --authfile - " - - local boolean_options=" - --help - -h - " - - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_volume_names - ;; - esac -} - -# __podman_complete_volumes applies completion of volumes based on the current -# value of `$cur` or the value of the optional first option `--cur`, if given. -__podman_complete_volumes() { - local current="$cur" - if [ "$1" = "--cur" ] ; then - current="$2" - shift 2 - fi - COMPREPLY=( $(compgen -W "$(__podman_volume "$@")" -- "$current") ) -} - -__podman_complete_volume_names() { - local names=( $(__podman_q volume ls --quiet) ) - COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") ) -} - - -_podman_attach() { - local options_with_args=" - --detach-keys - " - local boolean_options=" - --help -h - --latest -l - --no-stdin - --sig-proxy - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_container_attach() { - _podman_attach -} - -_podman_container_checkpoint() { - local options_with_args=" - -e - --export - " - local boolean_options=" - --all -a - --help -h - --ignore-rootfs - --keep -k - --latest -l - --leave-running -R - --tcp-established - " - case "$prev" in - -e|--export) - _filedir - return - ;; - esac - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_container_commit() { - _podman_commit -} - -_podman_container_cp() { - _podman_cp -} - -_podman_container_create() { - _podman_create -} - -_podman_container_diff() { - _podman_diff -} - -_podman_container_exec() { - _podman_exec -} - -_podman_container_export() { - _podman_export -} - -_podman_container_init() { - _podman_init -} - -_podman_container_inspect() { - _podman_inspect -} - -_podman_container_kill() { - _podman_kill -} - -_podman_container_list() { - _podman_ls -} - -_podman_container_ls() { - _podman_ls -} - -_podman_container_logs() { - _podman_logs -} - -_podman_container_mount() { - _podman_mount -} - -_podman_container_pause() { - _podman_pause -} - -_podman_container_port() { - _podman_port -} - -_podman_container_ps() { - _podman_ls -} - -_podman_container_refresh() { - local options_with_args=" - " - local boolean_options=" - --help - -h - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_container_restart() { - _podman_restart -} - -_podman_container_restore() { - local options_with_args=" - -i - --import - -n - --name - " - local boolean_options=" - --all -a - --help -h - --ignore-rootfs - --ignore-static-ip - --ignore-static-mac - --keep -k - --latest -l - --tcp-established - " - case "$prev" in - -i|--import) - _filedir - return - ;; - esac - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_created - ;; - esac -} - -_podman_container_rm() { - _podman_rm -} - -_podman_container_start() { - _podman_start -} - -_podman_container_stats() { - _podman_stats -} - -_podman_container_stop() { - _podman_stop -} - -_podman_container_top() { - _podman_top -} - -_podman_container_umount() { - _podman_umount -} - -_podman_container_unmount() { - _podman_unmount -} - -_podman_container_unpause() { - _podman_unpause -} - -_podman_container_wait() { - _podman_wait -} - -_podman_healthcheck() { - local boolean_options=" - --help - -h - " - subcommands=" - run - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_network() { - local boolean_options=" - --help - -h - " - subcommands=" - create - inspect - ls - rm - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_network_create() { - local options_with_args=" - -d - --driver - --gateway - --ip-range - --macvlan - --subnet - " - local boolean_options=" - --disable-dns - --help -h - --internal - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_network_inspect() { - local options_with_args=" - --format - -f - " - local boolean_options=" - --help - -h - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_network_ls() { - local options_with_args=" - --filter - --format -f - " - local boolean_options=" - --help - -h - --quiet - -q - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_network_rm() { - local options_with_args=" - " - local boolean_options=" - --force -f - --help -h - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_generate() { - local boolean_options=" - --help - -h - " - subcommands=" - kube - systemd - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_play() { - local boolean_options=" - --help - -h - " - subcommands=" - kube - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} -_podman_container() { - local boolean_options=" - --help - -h - " - subcommands=" - attach - checkpoint - commit - cp - create - diff - exec - exists - export - inspect - kill - list - logs - mount - pause - port - prune - refresh - restart - restore - rm - run - runlabel - start - stats - stop - top - umount - unmount - unpause - wait - " - local aliases=" - list - ps - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_system_reset() { - local options_with_args=" - " - local boolean_options=" - -h - --help - --force - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_system_df() { - local options_with_args=" - --format - --verbose - " - local boolean_options=" - -h - --help - --verbose - -v - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_system_info() { - _podman_info -} - -_podman_system_prune() { - local options_with_args=" - " - - local boolean_options=" - --all -a - --force -f - --help -h - --volumes - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_system_service() { - local options_with_args=" - -t - --timeout - " - local boolean_options=" - --help - -h - --varlink - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_system() { - local boolean_options=" - --help - -h - " - subcommands=" - df - info - migrate - prune - renumber - reset - service - " - __podman_subcommands "$subcommands" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_commit() { - local options_with_args=" - --author - -a - --change - -c - --message - -m - --iidfile - " - local boolean_options=" - --help -h - --pause -p - --quiet -q - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_build() { - local boolean_options=" - --force-rm - --help -h - --layers - --no-cache - --pull - --pull-always - --pull-never - --quiet -q - --rm - --squash - --squash-all - --tls-verify - " - - local options_with_args=" - --add-host - --annotation - --authfile - --build-arg - --cap-add - --cap-drop - --cgroup-parent - --cni-config-dir - --cni-plugin-path - --cpu-period - --cpu-quota - --cpu-shares - --cpuset-cpus - --cpuset-mems - --creds - --file -f - --format - --iidfile - --ipc - --label - --memory -m - --memory-swap - --mount - --net - --network - --pid - --runtime-flag - --security-opt - --shm-size - --tag -t - --ulimit - --userns - --userns-gid-map - --userns-gid-map-group - --userns-uid-map - --userns-uid-map-user - --uts - --volume -v - " - - case "$prev" in - --file|-f) - COMPREPLY=($(compgen -W "`ls`" -- "$cur")) - ;; - esac - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_diff() { - local options_with_args=" - --format - " - local boolean_options=" - --help - -h - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_exec() { - local options_with_args=" - --detach-keys - -e - --env - --env-file - --user - -u - --workdir - -w - " - local boolean_options=" - --help -h - --latest -l - --privileged - --tty -t - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac - -} -_podman_export() { - local options_with_args=" - --output - -o - " - local boolean_options=" - --help - -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_history() { - local options_with_args=" - --format - " - local boolean_options=" - --help -h - --human -H - --no-trunc - --quiet -q - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - - -_podman_import() { - local options_with_args=" - --change - -c - --message - -m - " - local boolean_options=" - --help - -h - --quiet - -q - " - case "$prev" in - --change|-c|--message|-m) - return - ;; - esac - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - local counter=$(__podman_pos_first_nonflag '--change|-c|--message|-m') - if [ "$cword" -eq "$counter" ]; then - _filedir - return - elif [ "$cword" -eq "$((counter + 1))" ]; then - __podman_complete_images --repo --tag - return - fi - ;; - esac -} - -_podman_info() { - local boolean_options=" - --help - -h - --debug - " - local options_with_args=" - -f - --format - " - - local all_options="$options_with_args $boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_list_images - ;; - esac -} - -_podman_image_umount() { - _podman_image_unmount -} - -_podman_image_unmount() { - local boolean_options=" - --all -a - --help -h - --force -f - " - local options_with_args=" - " - - local all_options="$options_with_args $boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --force-tag --id - ;; - esac -} - -_podman_image_mount() { - local boolean_options=" - --all -a - --help -h - " - - local options_with_args=" - --format - " - - local all_options="$options_with_args $boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --force-tag --id - ;; - esac -} - -_podman_image_build() { - _podman_build -} - -_podman_image_history() { - _podman_history -} - -_podman_image_import() { - _podman_import -} - -_podman_image_inspect() { - _podman_inspect -} - -_podman_image_load() { - _podman_load -} - -_podman_image_list() { - _podman_images -} - -_podman_image_ls() { - _podman_images -} - -_podman_image_pull() { - _podman_pull -} - -_podman_image_push() { - _podman_push -} - -_podman_image_rm() { - _podman_rmi -} - -_podman_image_save() { - _podman_save -} - -_podman_image_tag() { - _podman_tag -} - - -_podman_image_untag() { - _podman_untag -} - -_podman_image() { - local boolean_options=" - --help - -h - " - subcommands=" - build - exists - history - import - inspect - load - ls - mount - prune - pull - push - rm - save - sign - tag - trust - umount - unmount - untag - " - local aliases=" - list - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_images() { - local boolean_options=" - --all -a - --digests - --filter -f - --help -h - --history - --no-trunc - --noheading - --notruncate -n - --quiet -q - " - local options_with_args=" - --format - --sort - " - - local all_options="$options_with_args $boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_inspect() { - local boolean_options=" - --help -h - --latest -l - " - local options_with_args=" - --format - -f - --type - -t - --size - -s - " - - local all_options="$options_with_args $boolean_options" - - local preselected_type - local type - - if [ "$1" = "--type" ] ; then - preselected_type=yes - type="$2" - else - type=$(__podman_value_of_option --type) - fi - - case "$prev" in - --format|-f) - return - ;; - --type) - if [ -z "$preselected_type" ] ; then - COMPREPLY=( $( compgen -W "container image" -- "$cur" ) ) - return - fi - ;; - esac - - case "$cur" in - -*) - local options="--format -f --help --size -s" - if [ -z "$preselected_type" ] ; then - options+=" --type" - fi - COMPREPLY=( $( compgen -W "$options" -- "$cur" ) ) - ;; - *) - case "$type" in - '') - COMPREPLY=( $( compgen -W " - $(__podman_containers --all) - $(__podman_images --force-tag --id) - " -- "$cur" ) ) - __ltrim_colon_completions "$cur" - ;; - container) - __podman_complete_containers_all - ;; - image) - __podman_complete_images --force-tag --id - ;; - esac - esac -} - -_podman_kill() { - local options_with_args=" - --signal -s - " - local boolean_options=" - --all -a - --help -h - --latest -l - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_logs() { - local options_with_args=" - --since - --tail - " - local boolean_options=" - --follow -f - --help -h - --latest -l - --timestamps -t - " - _complete_ "$options_with_args" "$boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_list_containers - ;; - esac -} - -_podman_manifest() { - local boolean_options=" - --help - -h - " - subcommands=" - add - create - inspect - push - remove - " - __podman_subcommands "$subcommands" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_manifest_add() { - local options_with_args=" - --annotation - --arch - --authfile - --cert-dir - --creds - --features - --os - --os-version - --variant - " - - local boolean_options=" - --all - --help - -h - --tls-verify - " - - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_manifest_annotate() { - local options_with_args=" - --annotation - --arch - --features - --os - --os-features - --os-version - --variant - " - - local boolean_options=" - --help - -h - " - - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_manifest_create() { - local boolean_options=" - --all - --help - -h - " - - _complete_ "$boolean_options" -} - -_podman_manifest_inspect() { - local options_with_args=" - " - - local boolean_options=" - " - - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - - ;; - esac -} - -_podman_manifest_push() { - local options_with_args=" - --authfile - --cert-dir - --creds - --digestfile - --format -f - --sign-by - --signature-policy, - " - - local boolean_options=" - --all - --help -h - --purge - --quiet - --remove-signatures - --tls-verify - " - - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_manifest_remove() { - local options_with_args=" - " - - local boolean_options=" - " - - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_pull() { - local options_with_args=" - --authfile - --cert-dir - --creds - --override-arch - --override-os - --override-variant - " - local boolean_options=" - --all-tags -a - --disable-content-trust - --help -h - --quiet -q - --tls-verify - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_search() { - local options_with_args=" - --authfile - --filter -f - --format - --limit - " - local boolean_options=" - --help - -h - --no-trunc - --list-tags - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_unmount() { - _podman_umount $@ -} - -_podman_umount() { - local boolean_options=" - --all -a - --help -h - --force -f - --latest -l - " - local options_with_args=" - " - - local all_options="$options_with_args $boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_mount() { - local boolean_options=" - --all -a - --help -h - --latest -l - --notruncate - " - - local options_with_args=" - --format - " - - local all_options="$options_with_args $boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_push() { - local boolean_options=" - --compress - --digestflag - --help -h - --quiet -q - --tls-verify - " - - local options_with_args=" - --authfile - --cert-dir - --creds - --format - --sign-by - " - - local all_options="$options_with_args $boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_container_run() { - local options_with_args=" - --add-host - --annotation - --attach -a - --blkio-weight - --blkio-weight-device - --builtin-volume - --cap-add - --cap-drop - --cgroup-conf - --cgroup-parent - --cidfile - --conmon-pidfile - --cpu-period - --cpu-quota - --cpu-rt-period - --cpu-rt-runtime - --cpu-shares -c - --cpus - --cpuset-cpus - --cpuset-mems - --device - --device-cgroup-rule - --device-read-bps - --device-read-iops - --device-write-bps - --device-write-iops - --dns - --dns-option - --dns-search - --entrypoint - --env -e - --env-file - --env-host - --expose - --gidmap - --group-add - --health-cmd - --health-interval - --health-retries - --health-start-period - --health-timeout - --hostname -h - --http-proxy - --image-volume - --init-path - --ip - --ipc - --kernel-memory - --label -l - --label-file - --log-driver - --log-opt - --mac-address - --memory -m - --memory-reservation - --memory-swap - --memory-swappiness - --name - --network - --no-healthcheck - --no-hosts - --oom-score-adj - --override-arch - --override-os - --override-variant - --pid - --pids-limit - --pod - --pod-id-file - --preserve-fds - --publish -p - --pull - --restart - --rootfs - --runtime - --security-opt - --shm-size - --stop-signal - --stop-timeout - --subgidname - --subuidname - --sysctl - --systemd - --tmpfs - --tz - --uidmap - --ulimit - --umask - --user -u - --userns - --uts - --volume -v - --volumes-from - --workdir -w - " - - local boolean_options=" - --disable-content-trust=false - --help -h - --init - --interactive -i - --oom-kill-disable - --privileged - --publish-all -P - --quiet - --read-only - --read-only-tmpfs - --tty -t - " - - if [ "$command" = "run" -o "$subcommand" = "run" ] ; then - options_with_args="$options_with_args - --detach-keys - --health-cmd - --health-interval - --health-retries - --health-start-period - --health-timeout - " - boolean_options="$boolean_options - --detach -d - --rm - --rmi - --sig-proxy=false - " - __podman_complete_detach_keys && return - __podman_complete_restart && return - fi - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - return - ;; - *) - __podman_complete_images --id - ;; - esac - - - __podman_complete_log_driver_options && return - - local key=$(__podman_map_key_of_current_option '--security-opt') - case "$key" in - label) - [[ $cur == *: ]] && return - COMPREPLY=( $( compgen -W "user: role: type: level: disable" -- "${cur##*=}") ) - if [ "${COMPREPLY[*]}" != "disable" ] ; then - __podman_nospace - fi - return - ;; - seccomp) - local cur=${cur##*=} - _filedir - COMPREPLY+=( $( compgen -W "unconfined" -- "$cur" ) ) - return - ;; - esac - - case "$prev" in - --add-host) - case "$cur" in - *:) - __podman_complete_resolved_hostname - return - ;; - esac - ;; - --attach|-a) - COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) ) - return - ;; - --cap-add|--cap-drop) - __podman_complete_capabilities - return - ;; - --cidfile|--env-file|--init-path|--label-file|--pod-id-file) - _filedir - return - ;; - --device|--tmpfs|--volume|-v) - case "$cur" in - *:*) - # TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine) - ;; - '') - COMPREPLY=( $( compgen -W '/' -- "$cur" ) ) - __podman_nospace - ;; - /*) - _filedir - __podman_nospace - ;; - esac - return - ;; - --env|-e) - # we do not append a "=" here because "-e VARNAME" is legal syntax, too - COMPREPLY=( $( compgen -e -- "$cur" ) ) - __podman_nospace - return - ;; - --ipc) - case "$cur" in - *:*) - cur="${cur#*:}" - __podman_complete_containers_running - ;; - *) - COMPREPLY=( $( compgen -W 'host private container:' -- "$cur" ) ) - if [ "$COMPREPLY" = "container:" ]; then - __podman_nospace - fi - ;; - esac - return - ;; - --log-driver) - __podman_complete_log_drivers - return - ;; - --log-opt) - __podman_complete_log_options - return - ;; - --network) - case "$cur" in - container:*) - __podman_complete_containers_all --cur "${cur#*:}" - ;; - *) - COMPREPLY=( $(compgen -W "bridge none host ns: private slirp4netns $(__podman_networks) container:" -- "$cur")) - if [ "${COMPREPLY[*]}" = "container:" ] ; then - __podman_nospace - fi - ;; - esac - return - ;; - --pod) - __podman_complete_pod_names - return - ;; - --pid) - case "$cur" in - *:*) - __podman_complete_containers_running --cur "${cur#*:}" - ;; - *) - COMPREPLY=( $( compgen -W 'host container: private' -- "$cur" ) ) - if [ "$COMPREPLY" = "container:" ]; then - __podman_nospace - fi - ;; - esac - return - ;; - --runtime) - __podman_complete_runtimes - return - ;; - --security-opt) - COMPREPLY=( $( compgen -W "apparmor= label= no-new-privileges seccomp=" -- "$cur") ) - if [ "${COMPREPLY[*]}" != "no-new-privileges" ] ; then - __podman_nospace - fi - return - ;; - --storage-opt) - COMPREPLY=( $( compgen -W "size" -S = -- "$cur") ) - __podman_nospace - return - ;; - --user|-u) - __podman_complete_user_group - return - ;; - --userns) - COMPREPLY=( $( compgen -W "auto container: host keep-id ns: private" -- "$cur" ) ) - return - ;; - --volumes-from) - __podman_complete_containers_all - return - ;; - $(__podman_to_extglob "$options_with_args") ) - return - ;; - esac - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) ) - ;; - *) - local counter=$( __podman_pos_first_nonflag $( __podman_to_alternatives "$options_with_args" ) ) - if [ $cword -eq $counter ]; then - __podman_complete_images - fi - ;; - esac -} - - -_podman_create() { - _podman_container_run -} - - -_podman_run() { - _podman_container_run -} - -_podman_restart() { - local options_with_args=" - --time -t - " - local boolean_options=" - --all -a - --help -h - --latest -l - --running - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_rm() { - local boolean_options=" - --all -a - --cidfile - --force -f - --help -h - --ignore -i - --latest -l - --volumes -v - " - - local options_with_args=" - " - - local all_options="$options_with_args $boolean_options" - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_rmi() { - local boolean_options=" - --all -a - --force -f - --help -h - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_stats() { - local boolean_options=" - --all -a - --format - --help -h - --no-reset - --no-stream - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_tag() { - local options_with_args=" - " - local boolean_options=" - --help - -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images - ;; - esac -} - -_podman_untag() { - local options_with_args=" - " - local boolean_options=" - --help - -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images - ;; - esac -} - -__podman_top_descriptors() { - podman top --list-descriptors -} - -__podman_complete_top_descriptors() { - COMPREPLY=($(compgen -W "$(__podman_top_descriptors)" -- "$cur")) -} - -_podman_top() { - local options_with_args=" - " - local boolean_options=" - --help - -h - --latest - -l - " - - # podman-top works on only *one* container, which means that when we have - # three or more arguments, we can complete with top descriptors. - if [[ "${COMP_CWORD}" -ge 3 ]]; then - __podman_complete_top_descriptors - return +__podman_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" fi - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_version() { - local boolean_options=" - --help - -h - " - local options_with_args=" - --format - -f - " - local all_options="$options_with_args $boolean_options" - - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_save() { - local options_with_args=" - --output -o - --format - " - local boolean_options=" - --compress - --help -h - --quiet -q - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_pause() { - local boolean_options=" - --all - -a - --help - -h - " - local options_with_args=" - " - local boolean_options="" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_port() { - local options_with_args=" - " - local boolean_options=" - --all -a - --help -h - --latest -l - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_podman_ls() { - _podman_ps -} - -_podman_ps() { - local options_with_args=" - --filter -f - --format - --last -n - --sort - --watch -w - " - local boolean_options=" - --all -a - --external - --help -h - --latest -l - --namespace --ns - --no-trunc - --pod -p - --quiet -q - --size -s - --sync - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_init() { - local boolean_options=" - --all - -a - --help - -h - --latest - -l - " - local options_with_args=" - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_unpauseable - ;; - esac } -_podman_start() { - local options_with_args=" - --detach-keys - " - - local boolean_options=" - --attach -a - --help -h - --interactive -i - --latest -l - --sig-proxy - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_stopped - ;; - esac -} -_podman_stop() { - local options_with_args=" - --time -t - " - local boolean_options=" - --all -a - --cidfile - --help -h - --ignore -i - --latest -l - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; - esac -} - -_podman_unpause() { - local boolean_options=" - --all - -a - --help - -h - " - local options_with_args=" - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_unpauseable - ;; - esac -} - -_podman_varlink() { - local options_with_args=" - --timeout -t - " - local boolean_options=" - --help - -h - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_wait() { - local options_with_args="" - local boolean_options=" - --help -h - --interval -i - --latest -l - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; - esac -} - -_complete_() { - local options_with_args=$1 - local boolean_options="$2 -h --help" - - case "$prev" in - $options_with_args) - return - ;; - esac - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) - ;; - esac -} - -_podman_load() { - local options_with_args=" - --input -i - " - local boolean_options=" - --help -h - --quiet -q - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_cp() { - local boolean_options=" - --extract - --help -h - --pause - " - _complete_ "$boolean_options" -} - -_podman_login() { - local options_with_args=" - --authfile - --get-login - --password -p - --username -u - " - local boolean_options=" - --help - -h - --password-stdin - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_logout() { - local options_with_args=" - --authfile - " - local boolean_options=" - --all - -a - --help - -h - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_healthcheck_run() { - local options_with_args="" - - local boolean_options=" - -h - --help - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - COMPREPLY=( $( compgen -W " - $(__podman_containers --all) - " -- "$cur" ) ) - __ltrim_colon_completions "$cur" - ;; - esac -} - -_podman_generate_kube() { - local options_with_args=" - --filename -f - " - - local boolean_options=" - -h - --help - -s - --service - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - COMPREPLY=( $( compgen -W " - $(__podman_containers --all) - $(__podman_pods) - " -- "$cur" ) ) - __ltrim_colon_completions "$cur" - ;; - esac -} - -_podman_generate_systemd() { - local options_with_args=" - --restart-policy - -t - --time - --container-prefix - --pod-prefix - --separator" - - local boolean_options=" - -h - --help - -n - --name - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - COMPREPLY=( $( compgen -W " - $(__podman_containers --all) - " -- "$cur" ) ) - __ltrim_colon_completions "$cur" - ;; - esac -} - -_podman_play_kube() { - local options_with_args=" - --authfile - --cert-dir - --creds - --network - " - - local boolean_options=" - --help -h - --quiet -q - --seccomp-profile-root - --tls-verify - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - _filedir - ;; - esac -} - -_podman_events() { - local options_with_args=" - --filter - --format - --help -h - --since - --until - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$options_with_args" -- "$cur")) - ;; - esac -} - -_podman_container_runlabel() { - local options_with_args=" - --authfile - --cert-dir - --creds - --name - " - - local boolean_options=" - --display - --help -h - --quiet -q - --replace - --tls-verify - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; - esac -} - -_podman_image_sign() { - local options_with_args=" - --cert-dir - --directory -d - --sign-by - " - local boolean_options=" - --help - -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images - ;; - esac -} - -_podman_image_trust_set() { - echo hello - local options_with_args=" - --pubkeysfile -f - --type -t - " - local boolean_options=" - --help - -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - COMPREPLY=($(compgen -W "default $( __podman_list_registries )" -- "$cur")) - ;; - esac -} - -_podman_image_trust_show() { - local options_with_args=" - " - local boolean_options=" - --help -h - --json -j - --raw - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images - ;; - esac -} - -_podman_image_trust() { - local boolean_options=" - --help - -h - " - subcommands=" - set - show - " - local aliases=" - list - " - command=image_trust - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_images_prune() { - local options_with_args=" - " - - local boolean_options=" - --all -a - --help -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_container_prune() { - local options_with_args=" - --filter - " - - local boolean_options=" - --force -f - --help -h - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_container_exists() { - local options_with_args=" - " - - local boolean_options=" - --external - --help -h - " - - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images - ;; - esac - -} - -_podman_pod_exists() { - local options_with_args=" - " - - local boolean_options=" - " -} - -_podman_image_exists() { - local options_with_args=" - " - - local boolean_options=" - " -} - -_podman_pod_create() { - local options_with_args=" - --add-host - --cgroup-parent - --dns - --dns-opt - --dns-search - --infra-command - --infra-conmon-pidfile - --infra-image - --ip - --label-file - --label - -l - --mac-address - --name - --network - --no-hosts - --podidfile - --publish - -p - --share - " - - local boolean_options=" - --help -h - --infra - --replace - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_pod_kill() { - local options_with_args=" - " - - local boolean_options=" - --all -a - --help -h - --latest -l - --signal -s - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -__podman_pod_ps() { - local options_with_args=" - --filter -f - --format - --sort - " - - local boolean_options=" - --cgroup - --ctr-ids - --ctr-names - --ctr-status - --help -h - --labels - --latest -l - --no-trunc - --quiet -q - " - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_pod_ls() { - __podman_pod_ps -} - -_podman_pod_list() { - __podman_pod_ps -} - -_podman_pod_ps() { - __podman_pod_ps -} - -_podman_pod_prune() { - local options_with_args=" - " - - local boolean_options=" - -f - -h - --help - " - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - esac -} - -_podman_pod_restart() { - local options_with_args=" - " - - local boolean_options=" - --all -a - --help -h - --latest -l - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -_podman_pod_rm() { - local options_with_args=" - --pod-id-file - " - - local boolean_options=" - --all -a - --force -f - --help -h - --ignore -i - --latest -l - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -_podman_pod_start() { - local options_with_args=" - --pod-id-file - " - - local boolean_options=" - --all -a - --help -h - --latest -l - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -_podman_pod_stop() { - local options_with_args=" - --pod-id-file - --time -t - " - - local boolean_options=" - --all -a - --cleanup - --help -h - --ignore -i - --latest -l - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -_podman_pod_pause() { - local options_with_args=" - " - - local boolean_options=" - --all -a - --help -h - --latest -l - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -_podman_pod_unpause() { - local options_with_args=" - " - - local boolean_options=" - --all -a - --help -h - --latest -l - " - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; - esac -} - -_podman_pod_inspect() { - local options_with_args=" - --format -f - --latest -l - " - - _complete_ "$options_with_args" -} - - -_podman_pod() { - local boolean_options=" - --help - -h - " - subcommands=" - create - inspect - kill - pause - ps - restart - rm - start - stats - stop - top - unpause - " - local aliases=" - list - ls - " - __podman_subcommands "$subcommands $aliases" && return - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac -} - -_podman_volume_create() { - local options_with_args=" - --driver - --label -l - --opt -o - " - - local boolean_options=" - --help - -h - " - - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_volume_ls() { - local options_with_args=" - --filter - --format -f - " - - local boolean_options=" - --help -h - --quiet -q - " - - _complete_ "$options_with_args" "$boolean_options" -} - -_podman_volume_inspect() { - local options_with_args=" - --format - -f - " - - local boolean_options=" - --all -a - --help -h - " +__podman_perform_completion() +{ + __podman_debug + __podman_debug "========= starting completion logic ==========" + __podman_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $cword location, so we need + # to truncate the command-line ($words) up to the $cword location. + words=("${words[@]:0:$cword+1}") + __podman_debug "Truncated words[*]: ${words[*]}," + + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveLegacyCustomComp=32 + local shellCompDirectiveLegacyCustomArgsComp=64 + + local out requestComp lastParam lastChar comp directive args flagPrefix + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly podman allows to handle aliases + args=("${words[@]:1}") + requestComp="${words[0]} __completeNoDesc ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __podman_debug "lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __podman_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_volume_names - ;; - esac -} + # When completing a flag with an = (e.g., podman -n=<TAB>) + # bash focuses on the part after the =, so we need to remove + # the flag part from $cur + if [[ "${cur}" == -*=* ]]; then + flagPrefix="${cur%%=*}=" + cur="${cur#*=}" + fi -_podman_volume_rm() { - local options_with_args="" + __podman_debug "Calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __podman_debug "The completion directive is: ${directive}" + __podman_debug "The completions are: ${out[*]}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __podman_debug "Received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __podman_debug "Activating no space" + compopt -o nospace + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __podman_debug "Activating no file completion" + compopt +o default + fi + fi + fi - local boolean_options=" - --all -a - --force -f - --help -h - " + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out[*]}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __podman_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + + # Use printf to strip any trailing newline + local subdir + subdir=$(printf "%s" "${out[0]}") + if [ -n "$subdir" ]; then + __podman_debug "Listing directories in $subdir" + pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return + else + __podman_debug "Listing directories in ." + _filedir -d + fi + elif [ $((directive & shellCompDirectiveLegacyCustomComp)) -ne 0 ]; then + local cmd + __podman_debug "Legacy custom completion. Directive: $directive, cmds: ${out[*]}" + + # The following variables should get their value through the commands + # we have received as completions and are parsing below. + local last_command + local nouns + + # Execute every command received + while IFS='' read -r cmd; do + __podman_debug "About to execute: $cmd" + eval "$cmd" + done < <(printf "%s\n" "${out[@]}") + + __podman_debug "last_command: $last_command" + __podman_debug "nouns[0]: ${nouns[0]}, nouns[1]: ${nouns[1]}" + + if [ $((directive & shellCompDirectiveLegacyCustomArgsComp)) -ne 0 ]; then + # We should call the global legacy custom completion function, if it is defined + if declare -F __podman_custom_func >/dev/null; then + # Use command name qualified legacy custom func + __podman_debug "About to call: __podman_custom_func" + __podman_custom_func + elif declare -F __custom_func >/dev/null; then + # Otherwise fall back to unqualified legacy custom func for compatibility + __podman_debug "About to call: __custom_func" + __custom_func + fi + fi + else + local tab + tab=$(printf '\t') + local longest=0 + # Look for the longest completion so that we can format things nicely + while IFS='' read -r comp; do + comp=${comp%%$tab*} + if ((${#comp}>longest)); then + longest=${#comp} + fi + done < <(printf "%s\n" "${out[@]}") + + local completions=() + while IFS='' read -r comp; do + if [ -z "$comp" ]; then + continue + fi + + __podman_debug "Original comp: $comp" + comp="$(__podman_format_comp_descriptions "$comp" "$longest")" + __podman_debug "Final comp: $comp" + completions+=("$comp") + done < <(printf "%s\n" "${out[@]}") + + while IFS='' read -r comp; do + # Although this script should only be used for bash + # there may be programs that still convert the bash + # script into a zsh one. To continue supporting those + # programs, we do this single adaptation for zsh + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + COMPREPLY+=("$flagPrefix$comp") + else + COMPREPLY+=("$comp") + fi + done < <(compgen -W "${completions[*]}" -- "$cur") + + # If there is a single completion left, remove the description text + if [ ${#COMPREPLY[*]} -eq 1 ]; then + __podman_debug "COMPREPLY[0]: ${COMPREPLY[0]}" + comp="${COMPREPLY[0]%% *}" + __podman_debug "Removed description from single completion, which is now: ${comp}" + COMPREPLY=() + COMPREPLY+=("$comp") + fi + fi - _complete_ "$options_with_args" "$boolean_options" - case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_volume_names - ;; - esac + __podman_handle_special_char "$cur" : + __podman_handle_special_char "$cur" = } -_podman_volume_prune() { - local options_with_args="" - - local boolean_options=" - --force -f - --help -h - " - - _complete_ "$options_with_args" "$boolean_options" +__podman_handle_special_char() +{ + local comp="$1" + local char=$2 + if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then + local word=${comp%"${comp##*${char}}"} + local idx=${#COMPREPLY[*]} + while [[ $((--idx)) -ge 0 ]]; do + COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"} + done + fi } -_podman_volume() { - local boolean_options=" - --help -h - " - subcommands=" - create - inspect - ls - prune - rm - " - local aliases=" - list - remove - " - __podman_subcommands "$subcommands $aliases" && return +__podman_format_comp_descriptions() +{ + local tab + tab=$(printf '\t') + local comp="$1" + local longest=$2 + + # Properly format the description string which follows a tab character if there is one + if [[ "$comp" == *$tab* ]]; then + desc=${comp#*$tab} + comp=${comp%%$tab*} + + # $COLUMNS stores the current shell width. + # Remove an extra 4 because we add 2 spaces and 2 parentheses. + maxdesclength=$(( COLUMNS - longest - 4 )) + + # Make sure we can fit a description of at least 8 characters + # if we are to align the descriptions. + if [[ $maxdesclength -gt 8 ]]; then + # Add the proper number of spaces to align the descriptions + for ((i = ${#comp} ; i < longest ; i++)); do + comp+=" " + done + else + # Don't pad the descriptions so we can fit more text after the completion + maxdesclength=$(( COLUMNS - ${#comp} - 4 )) + fi + + # If there is enough space for any description text, + # truncate the descriptions that are too long for the shell width + if [ $maxdesclength -gt 0 ]; then + if [ ${#desc} -gt $maxdesclength ]; then + desc=${desc:0:$(( maxdesclength - 1 ))} + desc+="…" + fi + comp+=" ($desc)" + fi + fi - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac + # Must use printf to escape all special characters + printf "%q" "${comp}" } -_podman_podman() { - local options_with_args=" - --cni-config-dir - --conmon - --connection -c - --events-backend - --hooks-dir - --identity - --log-level - --namespace - --network-cmd-path - --root - --runroot - --runtime - --storage-driver - --storage-opt - --tmpdir - --url - " - local boolean_options=" - --help -h - --remote -r - --syslog - --version -v - " - commands=" - attach - auto-update - build - commit - container - cp - create - diff - events - exec - export - generate - healthcheck - history - image - images - import - info - inspect - kill - load - login - logout - logs - manifest - mount - network - pause - play - pod - port - ps - pull - push - restart - rm - rmi - run - save - search - start - stats - stop - system - tag - top - umount - unmount - unpause - untag - varlink - version - volume - wait - " +__start_podman() +{ + local cur prev words cword - case "$prev" in - $main_options_with_args_glob ) - return - ;; - esac + COMPREPLY=() + _get_comp_words_by_ref -n "=:" cur prev words cword - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) ) - ;; - esac + __podman_perform_completion } -_cli_bash_autocomplete() { - local cur opts base - - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=() - local cur prev words cword +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_podman podman +else + complete -o default -o nospace -F __start_podman podman +fi - _get_comp_words_by_ref -n : cur prev words cword - - local command=${PROG} cpos=0 - local counter=1 - counter=1 - while [ $counter -lt $cword ]; do - case "!${words[$counter]}" in - *) - command=$(echo "${words[$counter]}" | sed 's/-/_/g') - cpos=$counter - (( cpos++ )) - break - ;; - esac - (( counter++ )) - done - - local completions_func=_podman_${command} - declare -F $completions_func >/dev/null && $completions_func - - eval "$previous_extglob_setting" - return 0 -} +# ex: ts=4 sw=4 et filetype=sh -complete -F _cli_bash_autocomplete podman +# This file is generated with "podman completion"; see: podman-completion(1) diff --git a/completions/bash/podman-remote b/completions/bash/podman-remote new file mode 100644 index 000000000..43a5ce959 --- /dev/null +++ b/completions/bash/podman-remote @@ -0,0 +1,271 @@ +# bash completion for podman-remote -*- shell-script -*- + +__podman-remote_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +__podman-remote_perform_completion() +{ + __podman-remote_debug + __podman-remote_debug "========= starting completion logic ==========" + __podman-remote_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $cword location, so we need + # to truncate the command-line ($words) up to the $cword location. + words=("${words[@]:0:$cword+1}") + __podman-remote_debug "Truncated words[*]: ${words[*]}," + + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveLegacyCustomComp=32 + local shellCompDirectiveLegacyCustomArgsComp=64 + + local out requestComp lastParam lastChar comp directive args flagPrefix + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly podman-remote allows to handle aliases + args=("${words[@]:1}") + requestComp="${words[0]} __completeNoDesc ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __podman-remote_debug "lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __podman-remote_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + # When completing a flag with an = (e.g., podman-remote -n=<TAB>) + # bash focuses on the part after the =, so we need to remove + # the flag part from $cur + if [[ "${cur}" == -*=* ]]; then + flagPrefix="${cur%%=*}=" + cur="${cur#*=}" + fi + + __podman-remote_debug "Calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __podman-remote_debug "The completion directive is: ${directive}" + __podman-remote_debug "The completions are: ${out[*]}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __podman-remote_debug "Received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __podman-remote_debug "Activating no space" + compopt -o nospace + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __podman-remote_debug "Activating no file completion" + compopt +o default + fi + fi + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out[*]}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __podman-remote_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + + # Use printf to strip any trailing newline + local subdir + subdir=$(printf "%s" "${out[0]}") + if [ -n "$subdir" ]; then + __podman-remote_debug "Listing directories in $subdir" + pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return + else + __podman-remote_debug "Listing directories in ." + _filedir -d + fi + elif [ $((directive & shellCompDirectiveLegacyCustomComp)) -ne 0 ]; then + local cmd + __podman-remote_debug "Legacy custom completion. Directive: $directive, cmds: ${out[*]}" + + # The following variables should get their value through the commands + # we have received as completions and are parsing below. + local last_command + local nouns + + # Execute every command received + while IFS='' read -r cmd; do + __podman-remote_debug "About to execute: $cmd" + eval "$cmd" + done < <(printf "%s\n" "${out[@]}") + + __podman-remote_debug "last_command: $last_command" + __podman-remote_debug "nouns[0]: ${nouns[0]}, nouns[1]: ${nouns[1]}" + + if [ $((directive & shellCompDirectiveLegacyCustomArgsComp)) -ne 0 ]; then + # We should call the global legacy custom completion function, if it is defined + if declare -F __podman-remote_custom_func >/dev/null; then + # Use command name qualified legacy custom func + __podman-remote_debug "About to call: __podman-remote_custom_func" + __podman-remote_custom_func + elif declare -F __custom_func >/dev/null; then + # Otherwise fall back to unqualified legacy custom func for compatibility + __podman-remote_debug "About to call: __custom_func" + __custom_func + fi + fi + else + local tab + tab=$(printf '\t') + local longest=0 + # Look for the longest completion so that we can format things nicely + while IFS='' read -r comp; do + comp=${comp%%$tab*} + if ((${#comp}>longest)); then + longest=${#comp} + fi + done < <(printf "%s\n" "${out[@]}") + + local completions=() + while IFS='' read -r comp; do + if [ -z "$comp" ]; then + continue + fi + + __podman-remote_debug "Original comp: $comp" + comp="$(__podman-remote_format_comp_descriptions "$comp" "$longest")" + __podman-remote_debug "Final comp: $comp" + completions+=("$comp") + done < <(printf "%s\n" "${out[@]}") + + while IFS='' read -r comp; do + # Although this script should only be used for bash + # there may be programs that still convert the bash + # script into a zsh one. To continue supporting those + # programs, we do this single adaptation for zsh + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + COMPREPLY+=("$flagPrefix$comp") + else + COMPREPLY+=("$comp") + fi + done < <(compgen -W "${completions[*]}" -- "$cur") + + # If there is a single completion left, remove the description text + if [ ${#COMPREPLY[*]} -eq 1 ]; then + __podman-remote_debug "COMPREPLY[0]: ${COMPREPLY[0]}" + comp="${COMPREPLY[0]%% *}" + __podman-remote_debug "Removed description from single completion, which is now: ${comp}" + COMPREPLY=() + COMPREPLY+=("$comp") + fi + fi + + __podman-remote_handle_special_char "$cur" : + __podman-remote_handle_special_char "$cur" = +} + +__podman-remote_handle_special_char() +{ + local comp="$1" + local char=$2 + if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then + local word=${comp%"${comp##*${char}}"} + local idx=${#COMPREPLY[*]} + while [[ $((--idx)) -ge 0 ]]; do + COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"} + done + fi +} + +__podman-remote_format_comp_descriptions() +{ + local tab + tab=$(printf '\t') + local comp="$1" + local longest=$2 + + # Properly format the description string which follows a tab character if there is one + if [[ "$comp" == *$tab* ]]; then + desc=${comp#*$tab} + comp=${comp%%$tab*} + + # $COLUMNS stores the current shell width. + # Remove an extra 4 because we add 2 spaces and 2 parentheses. + maxdesclength=$(( COLUMNS - longest - 4 )) + + # Make sure we can fit a description of at least 8 characters + # if we are to align the descriptions. + if [[ $maxdesclength -gt 8 ]]; then + # Add the proper number of spaces to align the descriptions + for ((i = ${#comp} ; i < longest ; i++)); do + comp+=" " + done + else + # Don't pad the descriptions so we can fit more text after the completion + maxdesclength=$(( COLUMNS - ${#comp} - 4 )) + fi + + # If there is enough space for any description text, + # truncate the descriptions that are too long for the shell width + if [ $maxdesclength -gt 0 ]; then + if [ ${#desc} -gt $maxdesclength ]; then + desc=${desc:0:$(( maxdesclength - 1 ))} + desc+="…" + fi + comp+=" ($desc)" + fi + fi + + # Must use printf to escape all special characters + printf "%q" "${comp}" +} + +__start_podman-remote() +{ + local cur prev words cword + + COMPREPLY=() + _get_comp_words_by_ref -n "=:" cur prev words cword + + __podman-remote_perform_completion +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_podman-remote podman-remote +else + complete -o default -o nospace -F __start_podman-remote podman-remote +fi + +# ex: ts=4 sw=4 et filetype=sh + +# This file is generated with "podman-remote completion"; see: podman-completion(1) diff --git a/completions/fish/podman-remote.fish b/completions/fish/podman-remote.fish new file mode 100644 index 000000000..e0216844d --- /dev/null +++ b/completions/fish/podman-remote.fish @@ -0,0 +1,182 @@ +# fish completion for podman-remote -*- shell-script -*- + +function __podman_remote_debug + set file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __podman_remote_perform_completion + __podman_remote_debug "Starting __podman_remote_perform_completion" + + set args (string split -- " " (commandline -c)) + set lastArg "$args[-1]" + + __podman_remote_debug "args: $args" + __podman_remote_debug "last arg: $lastArg" + + set emptyArg "" + if test -z "$lastArg" + __podman_remote_debug "Setting emptyArg" + set emptyArg \"\" + end + __podman_remote_debug "emptyArg: $emptyArg" + + if not type -q "$args[1]" + # This can happen when "complete --do-complete podman-remote" is called when running this script. + __podman_remote_debug "Cannot find $args[1]. No completions." + return + end + + set requestComp "$args[1] __complete $args[2..-1] $emptyArg" + __podman_remote_debug "Calling $requestComp" + + set results (eval $requestComp 2> /dev/null) + set comps $results[1..-2] + set directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>) + # completions must be prefixed with the flag + set flagPrefix (string match -r -- '-.*=' "$lastArg") + + __podman_remote_debug "Comps: $comps" + __podman_remote_debug "DirectiveLine: $directiveLine" + __podman_remote_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%s%s\n" "$flagPrefix" "$comp" + end + + printf "%s\n" "$directiveLine" +end + +# This function does two things: +# - Obtain the completions and store them in the global __podman_remote_comp_results +# - Return false if file completion should be performed +function __podman_remote_prepare_completions + __podman_remote_debug "" + __podman_remote_debug "========= starting completion logic ==========" + + # Start fresh + set --erase __podman_remote_comp_results + + set results (__podman_remote_perform_completion) + __podman_remote_debug "Completion results: $results" + + if test -z "$results" + __podman_remote_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + return 1 + end + + set directive (string sub --start 2 $results[-1]) + set --global __podman_remote_comp_results $results[1..-2] + + __podman_remote_debug "Completions are: $__podman_remote_comp_results" + __podman_remote_debug "Directive is: $directive" + + set shellCompDirectiveError 1 + set shellCompDirectiveNoSpace 2 + set shellCompDirectiveNoFileComp 4 + set shellCompDirectiveFilterFileExt 8 + set shellCompDirectiveFilterDirs 16 + set shellCompDirectiveLegacyCustomComp 32 + set shellCompDirectiveLegacyCustomArgsComp 64 + + if test -z "$directive" + set directive 0 + end + + set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2) + if test $compErr -eq 1 + __podman_remote_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + return 1 + end + + set legacyCustom (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomComp) % 2) + set legacyCustomArgs (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomArgsComp) % 2) + if test $legacyCustom -eq 1; or test $legacyCustomArgs -eq 1 + __podman_remote_debug "Legacy bash custom completion not applicable to fish" + # Do full file completion instead + set --global __podman_remote_comp_do_file_comp 1 + return 1 + end + + set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2) + set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2) + if test $filefilter -eq 1; or test $dirfilter -eq 1 + __podman_remote_debug "File extension filtering or directory filtering not supported" + # Do full file completion instead + return 1 + end + + set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2) + set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2) + + __podman_remote_debug "nospace: $nospace, nofiles: $nofiles" + + # If we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set prefix (commandline -t) + __podman_remote_debug "prefix: $prefix" + + set completions + for comp in $__podman_remote_comp_results + if test (string match -e -r "^$prefix" "$comp") + set -a completions $comp + end + end + set --global __podman_remote_comp_results $completions + __podman_remote_debug "Filtered completions are: $__podman_remote_comp_results" + + # Important not to quote the variable for count to work + set numComps (count $__podman_remote_comp_results) + __podman_remote_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # To support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + # We must first split on \t to get rid of the descriptions because + # the extra character we add to the fake second completion must be + # before the description. We don't need descriptions anyway since + # there is only a single real completion which the shell will expand + # immediately. + __podman_remote_debug "Adding second completion to perform nospace directive" + set split (string split --max 1 \t $__podman_remote_comp_results[1]) + set --global __podman_remote_comp_results $split[1] $split[1]. + __podman_remote_debug "Completions are now: $__podman_remote_comp_results" + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __podman_remote_debug "Requesting file completion" + return 1 + end + end + + return 0 +end + +# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves +# so we can properly delete any completions provided by another script. +# The space after the the program name is essential to trigger completion for the program +# and not completion of the program name itself. +complete --do-complete "podman-remote " > /dev/null 2>&1 +# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. + +# Remove any pre-existing completions for the program since we will be handling all of them. +complete -c podman-remote -e + +# The call to __podman_remote_prepare_completions will setup __podman_remote_comp_results +# which provides the program's completion choices. +complete -c podman-remote -n '__podman_remote_prepare_completions' -f -a '$__podman_remote_comp_results' + + +# This file is generated with "podman-remote completion"; see: podman-completion(1) diff --git a/completions/fish/podman.fish b/completions/fish/podman.fish new file mode 100644 index 000000000..2a89e7118 --- /dev/null +++ b/completions/fish/podman.fish @@ -0,0 +1,182 @@ +# fish completion for podman -*- shell-script -*- + +function __podman_debug + set file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __podman_perform_completion + __podman_debug "Starting __podman_perform_completion" + + set args (string split -- " " (commandline -c)) + set lastArg "$args[-1]" + + __podman_debug "args: $args" + __podman_debug "last arg: $lastArg" + + set emptyArg "" + if test -z "$lastArg" + __podman_debug "Setting emptyArg" + set emptyArg \"\" + end + __podman_debug "emptyArg: $emptyArg" + + if not type -q "$args[1]" + # This can happen when "complete --do-complete podman" is called when running this script. + __podman_debug "Cannot find $args[1]. No completions." + return + end + + set requestComp "$args[1] __complete $args[2..-1] $emptyArg" + __podman_debug "Calling $requestComp" + + set results (eval $requestComp 2> /dev/null) + set comps $results[1..-2] + set directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>) + # completions must be prefixed with the flag + set flagPrefix (string match -r -- '-.*=' "$lastArg") + + __podman_debug "Comps: $comps" + __podman_debug "DirectiveLine: $directiveLine" + __podman_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%s%s\n" "$flagPrefix" "$comp" + end + + printf "%s\n" "$directiveLine" +end + +# This function does two things: +# - Obtain the completions and store them in the global __podman_comp_results +# - Return false if file completion should be performed +function __podman_prepare_completions + __podman_debug "" + __podman_debug "========= starting completion logic ==========" + + # Start fresh + set --erase __podman_comp_results + + set results (__podman_perform_completion) + __podman_debug "Completion results: $results" + + if test -z "$results" + __podman_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + return 1 + end + + set directive (string sub --start 2 $results[-1]) + set --global __podman_comp_results $results[1..-2] + + __podman_debug "Completions are: $__podman_comp_results" + __podman_debug "Directive is: $directive" + + set shellCompDirectiveError 1 + set shellCompDirectiveNoSpace 2 + set shellCompDirectiveNoFileComp 4 + set shellCompDirectiveFilterFileExt 8 + set shellCompDirectiveFilterDirs 16 + set shellCompDirectiveLegacyCustomComp 32 + set shellCompDirectiveLegacyCustomArgsComp 64 + + if test -z "$directive" + set directive 0 + end + + set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2) + if test $compErr -eq 1 + __podman_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + return 1 + end + + set legacyCustom (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomComp) % 2) + set legacyCustomArgs (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomArgsComp) % 2) + if test $legacyCustom -eq 1; or test $legacyCustomArgs -eq 1 + __podman_debug "Legacy bash custom completion not applicable to fish" + # Do full file completion instead + set --global __podman_comp_do_file_comp 1 + return 1 + end + + set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2) + set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2) + if test $filefilter -eq 1; or test $dirfilter -eq 1 + __podman_debug "File extension filtering or directory filtering not supported" + # Do full file completion instead + return 1 + end + + set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2) + set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2) + + __podman_debug "nospace: $nospace, nofiles: $nofiles" + + # If we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set prefix (commandline -t) + __podman_debug "prefix: $prefix" + + set completions + for comp in $__podman_comp_results + if test (string match -e -r "^$prefix" "$comp") + set -a completions $comp + end + end + set --global __podman_comp_results $completions + __podman_debug "Filtered completions are: $__podman_comp_results" + + # Important not to quote the variable for count to work + set numComps (count $__podman_comp_results) + __podman_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # To support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + # We must first split on \t to get rid of the descriptions because + # the extra character we add to the fake second completion must be + # before the description. We don't need descriptions anyway since + # there is only a single real completion which the shell will expand + # immediately. + __podman_debug "Adding second completion to perform nospace directive" + set split (string split --max 1 \t $__podman_comp_results[1]) + set --global __podman_comp_results $split[1] $split[1]. + __podman_debug "Completions are now: $__podman_comp_results" + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __podman_debug "Requesting file completion" + return 1 + end + end + + return 0 +end + +# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves +# so we can properly delete any completions provided by another script. +# The space after the the program name is essential to trigger completion for the program +# and not completion of the program name itself. +complete --do-complete "podman " > /dev/null 2>&1 +# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. + +# Remove any pre-existing completions for the program since we will be handling all of them. +complete -c podman -e + +# The call to __podman_prepare_completions will setup __podman_comp_results +# which provides the program's completion choices. +complete -c podman -n '__podman_prepare_completions' -f -a '$__podman_comp_results' + + +# This file is generated with "podman completion"; see: podman-completion(1) diff --git a/completions/zsh/_podman b/completions/zsh/_podman index b65c3dbb8..b25e9cb08 100644 --- a/completions/zsh/_podman +++ b/completions/zsh/_podman @@ -1,379 +1,181 @@ -#compdef podman +#compdef _podman podman -# To get zsh to reread this file: unset -f _podman;rm -f ~/.zcompdump;compinit +# zsh completion for podman -*- shell-script -*- -# On rereads, reset cache. (Not that the caching works, but some day it might) -unset -m '_podman_*' - -############################################################################### -# BEGIN 'podman help' parsers -- for options, subcommands, and usage - -# Run 'podman XX --help', set _podman_commands to a formatted list of cmds -_read_podman_commands() { - local line - - # Cache: the intention here is to run each 'podman help' only once. - # Unfortunately it doesn't seem to actually be working: even though - # I can see the var_ref in my shell, it's not visible here. - local _var_ref=_podman_commands_"${*// /_}" - typeset -ga _podman_commands - _podman_commands=(${(P)_var_ref}) - (( $#_podman_commands )) && return - - _call_program podman podman "$@" --help |\ - sed -n -e '0,/^Available Commands/d' -e '/^[A-Z]/q;p' |\ - sed -e 's/^ \+\([^ ]\+\) \+/\1:/' |\ - egrep . | while read line; do - _podman_commands+=($line) - done - - eval "typeset -ga $_var_ref" - eval "$_var_ref=(\$_podman_commands)" -} - -# Run 'podman XX --help', set _podman_option_list to a formatted list -# of option options for XX -_read_podman_options() { - local line - - local _var_ref=_podman_options_"${*// /_}" - eval "typeset -ga ${_var_ref}" - typeset -ga _podman_option_list - _podman_option_list=(${(P)_var_ref}) - (( $#_podman_option_list )) && return - - # Extract the Options; strip leading whitespace; pack '-f, --foo' - # as '-f,--foo' (no space); then add '=' to '--foo string'. - # The result will be, e.g. '-f,--foo=string Description of Option' - _call_program podman podman "$@" --help |\ - sed -n -e '0,/^Options:/d' -e '/^$/q;p' |\ - grep '^ \+-' |\ - sed -e 's/^ *//' -e 's/^\(-.,\) --/\1--/' |\ - sed -e 's/^\(-[^ ]\+\) \([^ ]\+\) /\1=\2 /' |\ - while read options desc;do - # options like --foo=string: split into --foo & string - local -a tmpa - local optval= - tmpa=(${(s.=.)options}) - if [ -n "$tmpa[2]" ]; then - options=$tmpa[1] - optval=$tmpa[2] - fi - - # 'podman attach --detach-keys' includes ']' in help msg - desc=${desc//\]/\\]} - - for option in ${(s:,:)options}; do - if [ -n "$optval" ]; then - _podman_option_list+=("${option}[$desc]:$(_podman_find_helper ${options} ${optval} ${desc})") - else - _podman_option_list+=("${option}[$desc]") - fi - done - done - - eval "typeset -ga $_var_ref=(\$_podman_option_list)" -} - -# Run 'podman XXX --help', set _podman_usage to the line after "Usage:" -_read_podman_usage() { - local _var_ref=_podman_usage_"${*// /_}" - typeset -ga _podman_usage - _podman_usage=${(P)_var_ref} - (( $#_podman_usage )) && return - - _podman_usage=$(_call_program podman podman "$@" --help |\ - grep -A1 'Usage:'|\ - tail -1 |\ - sed -e 's/^ *//') - - eval "typeset -ga $_var_ref" - eval "$_var_ref=\$_podman_usage" -} - -# END 'podman help' parsers -############################################################################### -# BEGIN custom helpers for individual option arguments - -# Find a zsh helper for a given option or command-line option -_podman_find_helper() { - local options=$1 - local optval=$2 - local desc=$3 - local helper= - - # Yes, this is a lot of hardcoding. IMHO it's still better than - # hardcoding every possible podman option. - # FIXME: there are many more options that could use helpers. - if expr "$desc" : ".*[Dd]irectory" >/dev/null; then - optval="directory" - helper="_files -/" - elif expr "$desc" : ".*[Pp]ath" >/dev/null; then - optval="path" - helper=_files - # For messages like 'restart policy ("always"|"no"|"on-failure") - elif optlist=$(expr "$desc" : '.*(\(\"[^\\)]\+|[^\\)]\+\"\))' 2>/dev/null); then - optval=${${options##--}//-/ } # "--log-level" => "log level" - optlist=${optlist//\"/} # "a"|"b"|"c" => a|b|c - optlist=${optlist//\|/ } # a|b|c => a b c - # FIXME: how to present values _in order_, not sorted alphabetically? - helper="($optlist)" +__podman_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" fi - echo "$optval:$helper" -} - -# END custom helpers for individual option arguments -############################################################################### -# BEGIN helpers for command-line args (containers, images) - -__podman_helper_generic() { - local expl line - local -a results - - local foo1=$1; shift - local name=$2; shift - - _call_program $foo1 podman "$@" |\ - while read line; do - results+=(${=line}) - done - - _wanted $foo1 expl $name compadd ${(u)results} -} - -_podman_helper_image() { - __podman_helper_generic podman-images 'images' \ - images --format '{{.ID}}\ {{.Repository}}:{{.Tag}}' -} - -# FIXME: at some point, distinguish between running & stopped containers -_podman_helper_container() { - __podman_helper_generic podman-containers 'containers' \ - ps -a --format '{{.Names}}\ {{.ID}}' -} - -_podman_helper_pod() { - __podman_helper_generic podman-pods 'pods' pod list --format '{{.Name}}' } -_podman_helper_volume() { - __podman_helper_generic podman-volumes 'volumes' volume ls --format '{{.Name}}' -} - -# Combinations. This one seen in diff & inspect -_podman_helper_container-or-image() { - _podman_helper_image - _podman_helper_container -} - -# Seen in generate-kube -_podman_helper_container-or-pod() { - _podman_helper_container - _podman_helper_pod -} - -# For top and pod-top -_podman_helper_format-descriptors() { - __podman_helper_generic top-format-descriptors 'format descriptors' \ - top --list-descriptors -} - -# for push, login/logout, and trust -# FIXME: some day, use this to define a helper for IMAGE-PATH (in 'pull') -_podman_helper_registry() { - local expl - local -a registries +_podman() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveLegacyCustomComp=32 + local shellCompDirectiveLegacyCustomArgsComp=64 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace + local -a completions + + __podman_debug "\n========= starting completion logic ==========" + __podman_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __podman_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __podman_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., podman -n=<TAB>) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi - # Suggestions for improvement more than welcome. - python3 -c 'from configparser import ConfigParser;cp=ConfigParser();cp.read("/etc/containers/registries.conf");registries=eval(cp.get("registries.search","registries"));[print(r) for r in registries]' 2>/dev/null | while read line; do - registries+=($line) - done + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __podman_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi - if (( $#registries )); then - _wanted podman-registry expl "registry" compadd ${(u)registries} + __podman_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __podman_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __podman_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} else - _hosts + # There is no directive specified. Leave $out as is. + __podman_debug "No directive found. Setting do default" + directive=0 fi -} - -# END helpers for command-line args -############################################################################### -# BEGIN figure out completion helpers for a given (sub)command -# Read Usage string for this subcommand, set up helpers for its subargs -_set_up_podman_args() { - _read_podman_usage "$@" + __podman_debug "directive: ${directive}" + __podman_debug "completions: ${out}" + __podman_debug "flagPrefix: ${flagPrefix}" - typeset -ga _podman_args=() - # E.g. 'podman exec [options] CONTAINER [...' -> 'CONTAINER [....' - local usage_rhs=$(expr "$_podman_usage" : ".*\[options\] \+\(.*\)") - - # e.g. podman pod ps which takes no further args - if [ -z "$usage_rhs" ]; then + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __podman_debug "Completion received error. Ignoring completions." return fi - # podman diff & inspect accept 'CONTAINER | IMAGE'; make into one keyword. - usage_rhs=${usage_rhs// | /-OR-} + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} - # Arg parsing. There are three possibilities in Usage messages: - # - # [IMAGE] - optional image arg (zero or one) - # IMAGE - exactly one image arg - # IMAGE [IMAGE...] - one or more image args - # and, theoretically: - # [IMAGE...] - zero or more? Haven't seen it in practice. Defer. - # - # For completion purposes, we only need to provide two options: - # one, or more than one? That is: continue offering completion - # suggestions after the first one? For that, we make two passes; - # in the first, mark an option as either '' (only one) or + local tab=$(printf '\t') + comp=${comp//$tab/:} - # Parse each command-line arg seen in usage message - local word - local -A _seen=() - for word in ${=usage_rhs}; do - local unbracketed=$(expr "$word" : "\[\(.*\)\]") + __podman_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") - if [ -n "$unbracketed" ]; then - # Remove all dots; assume(!?) that they'll all be at the end - unbracketed=${unbracketed//./} + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __podman_debug "Activating nospace." + noSpace="-S ''" + fi - if (( $_seen[$unbracketed] )); then - # Is this the same word as the previous arg? - if expr "$_podman_args[-1]" : ":$unbracketed:" >/dev/null; then - # Yes. Make it '*:...' instead of ':...', indicating >1 - _podman_args[-1]="*$_podman_args[-1]" - fi - continue + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" fi - - word=$unbracketed + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __podman_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subDir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __podman_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __podman_debug "Listing directories in ." fi - # As of 2019-03 all such instances are '[COMMAND [ARG...]]' and are, - # of course, at the end of the line. We can't offer completion for - # these, because the container will have different commands than - # the host system... but try anyway. - if [ "$word" = '[COMMAND' ]; then - # e.g. podman create, exec, run - _podman_args+=( - ":command: _command_names -e" - "*::arguments: _normal" - ) - return + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 fi + return $result + else + __podman_debug "Calling _describe" + if eval _describe "completions" completions $flagPrefix $noSpace; then + __podman_debug "_describe found some completions" - # Look for an existing helper, e.g. IMAGE -> _podman_helper_image - local helper="_podman_helper_${(L)word}" - if (( $+functions[$helper] )); then - : + # Return the success of having called _describe + return 0 else - # No defined helper. Reset, but check for known expressions. - helper= - case "$word" in - KUBEFILE) helper='_files -g "*.y(|a)ml(-.)"' ;; - PATH) helper='_files' ;; - esac - fi - - # Another special case: 'top' actually takes multiple options - local multi= - if [ "$word" = "FORMAT-DESCRIPTORS" ]; then - multi='*' + __podman_debug "_describe did not find completions." + __podman_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __podman_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __podman_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi fi - _podman_args+=("$multi:${(L)word}:$helper") - _seen[$word]=1 - done -} - -# For an endpoint command, i.e. not a subcommand. -_podman_terminus() { - typeset -A opt_args - typeset -ga _podman_option_list - typeset -ga _podman_args - integer ret=1 - - # Find out what args it takes (e.g. image(s), container(s)) and see - # if we have helpers for them. - _set_up_podman_args "$@" - _arguments -C $_podman_option_list $_podman_args && ret=0 - - return ret -} - -# END figure out completion helpers for a given (sub)command -################################################################################ -# BEGIN actual entry point - -# This is the main entry point; it's also where we (recursively) come in -# to handle nested subcommands such as 'podman container' or even 3-level -# ones like 'podman generate kube'. Nesting is complicated, so here's -# my best understanding as of 2019-03-12: -# -# Easy first: when you do "podman <TAB>" zsh calls us, we run 'podman --help', -# figure out the global options and subcommands, and run the magic _arguments -# command. That will offer those options/subcommands, and complete, etc. -# -# Where it gets harder is when you do "podman container mount <TAB>". -# zsh first calls us with words=(podman container mount) but we don't -# want all that full context yet! We want to go a piece at a time, -# handling 'container' first, then 'mount'; ending up with our -# final 'podman container mount --help' giving us suitable options -# and no subcommands; from which we determine that it's a terminus -# and jump to a function that handles non-subcommand arguments. -# -# This is the closest I've yet come to understanding zsh completion; -# it is still incomplete and may in fact be incorrect. But it works -# better than anything I've played with so far. -_podman_subcommand() { - local curcontext="$curcontext" state line - typeset -A opt_args - integer ret=1 - - # Run 'podman --help' / 'podman system --help' for our context (initially - # empty, then possibly under subcommands); from those, get a list of - # options appropriate for this context and, if applicable, subcommands. - _read_podman_options "$@" - _read_podman_commands "$@" - - # Now, is this a sub-subcommand, or does it have args? - if (( $#_podman_commands )); then - # Subcommands required (podman, podman system, etc) - local cmd=${words// /_} - _arguments -C $_podman_option_list \ - "(-): :->command" \ - "(-)*:: :->option-or-argument" \ - && ret=0 - - case $state in - (command) - _describe -t command "podman $* command" _podman_commands && ret=0 - ;; - (option-or-argument) - # I think this is when we have a _completed_ subcommand. - # Recurse back into here, offering options for that subcommand. - curcontext=${curcontext%:*:*}:podman-${words[1]}: - _podman_subcommand "$@" ${words[1]} && ret=0 - ;; - esac - else - # At a terminus, i.e. podman info, podman history; find out - # what args it takes. - _podman_terminus "$@" && ret=0 fi - - return ret } -_podman() { - _podman_subcommand -} +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_podman" ]; then + _podman +fi -# Local Variables: -# mode: shell-script -# sh-indentation: 4 -# indent-tabs-mode: nil -# sh-basic-offset: 4 -# End: -# vim: ft=zsh sw=4 ts=4 et +# This file is generated with "podman completion"; see: podman-completion(1) diff --git a/completions/zsh/_podman-remote b/completions/zsh/_podman-remote new file mode 100644 index 000000000..fc2984cfd --- /dev/null +++ b/completions/zsh/_podman-remote @@ -0,0 +1,181 @@ +#compdef _podman-remote podman-remote + +# zsh completion for podman-remote -*- shell-script -*- + +__podman-remote_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi +} + +_podman-remote() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveLegacyCustomComp=32 + local shellCompDirectiveLegacyCustomArgsComp=64 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace + local -a completions + + __podman-remote_debug "\n========= starting completion logic ==========" + __podman-remote_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __podman-remote_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __podman-remote_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., podman-remote -n=<TAB>) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __podman-remote_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __podman-remote_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __podman-remote_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __podman-remote_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __podman-remote_debug "No directive found. Setting do default" + directive=0 + fi + + __podman-remote_debug "directive: ${directive}" + __podman-remote_debug "completions: ${out}" + __podman-remote_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __podman-remote_debug "Completion received error. Ignoring completions." + return + fi + + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab=$(printf '\t') + comp=${comp//$tab/:} + + __podman-remote_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") + + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __podman-remote_debug "Activating nospace." + noSpace="-S ''" + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __podman-remote_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subDir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __podman-remote_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __podman-remote_debug "Listing directories in ." + fi + + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + return $result + else + __podman-remote_debug "Calling _describe" + if eval _describe "completions" completions $flagPrefix $noSpace; then + __podman-remote_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 + else + __podman-remote_debug "_describe did not find completions." + __podman-remote_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __podman-remote_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __podman-remote_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi + fi + fi +} + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_podman-remote" ]; then + _podman-remote +fi + +# This file is generated with "podman-remote completion"; see: podman-completion(1) diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 2a3041ccc..ee8ce4d45 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -499,6 +499,7 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_bindir}/%{name} %{_datadir}/bash-completion/completions/* %{_datadir}/zsh/site-functions/* +%{_datadir}/fish/vendor_completions.d/* %{_libexecdir}/%{name}/conmon %config(noreplace) %{_sysconfdir}/cni/net.d/87-%{name}-bridge.conflist %{_unitdir}/podman-auto-update.service diff --git a/contrib/systemd/auto-update/podman-auto-update.service b/contrib/systemd/auto-update/podman-auto-update.service index b63f24230..068dab95b 100644 --- a/contrib/systemd/auto-update/podman-auto-update.service +++ b/contrib/systemd/auto-update/podman-auto-update.service @@ -5,6 +5,7 @@ Wants=network.target After=network-online.target [Service] +Type=oneshot ExecStart=/usr/bin/podman auto-update [Install] diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst index 881dcb4b6..cd5d894da 100644 --- a/docs/source/Commands.rst +++ b/docs/source/Commands.rst @@ -3,7 +3,7 @@ Commands ======== -:doc:`Podman <markdown/podman.1>` (Pod Manager) Global Options +:doc:`Podman <markdown/podman.1>` (Pod Manager) Global Options, Environment Variables, Exit Codes, Configuration Files, and more :doc:`attach <markdown/podman-attach.1>` Attach to a running container diff --git a/docs/source/conf.py b/docs/source/conf.py index 6adaf4308..aad458a9b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,9 +17,9 @@ # -- Project information ----------------------------------------------------- -project = 'Podman' -copyright = '2019, team' -author = 'team' +project = "Podman" +copyright = "2019, team" +author = "team" # -- General configuration --------------------------------------------------- @@ -28,33 +28,33 @@ author = 'team' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'recommonmark', + "recommonmark", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] -master_doc = 'index' +master_doc = "index" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] html_css_files = [ - 'custom.css', + "custom.css", ] # -- Extension configuration ------------------------------------------------- diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 130d3365e..4570bf3ff 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -219,6 +219,13 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +#### **--decryption-key**=*key[:passphrase]* + +The [key[:passphrase]] to be used for decryption of images. Key can point to +keys and/or certificates. Decryption will be tried with all keys. If the key is +protected by a passphrase, it is required to be passed in the argument and +omitted otherwise. + #### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] Add a host device to the container. Optional *permissions* parameter @@ -394,7 +401,7 @@ The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. -#### **--network**=*mode* +#### **--network**=*mode*, **--net** Sets the configuration for network namespaces when handling `RUN` instructions. diff --git a/docs/source/markdown/podman-completion.1.md b/docs/source/markdown/podman-completion.1.md new file mode 100644 index 000000000..50b0b6145 --- /dev/null +++ b/docs/source/markdown/podman-completion.1.md @@ -0,0 +1,56 @@ +% podman-completion(1) + +## NAME +podman\-completion - Generate shell completion scripts + +## SYNOPSIS +**podman completion** [*options*] *bash*|*zsh*|*fish* + +## DESCRIPTION +The completion command allows you to generate shell completion scripts. Supported shells are **bash**, **zsh** and **fish**. + +These script are used by the shell to provide suggestions and complete commands when you are typing the command and press [TAB]. + +Usually these scripts are automatically installed via the package manager. + +## OPTIONS +#### **--file**, **-f** + +Write the generated output to file. + +#### **--no-desc** + +Do not provide description in the completions. + +## Installation + +### BASH +Make sure you have `bash-completion` installed on your system. + +To load the completion script into your current session run: +`source <(podman completion bash)` + +To make it available in all your bash sessions run: +`podman completion bash -f /etc/bash_completion.d/podman` + + +### ZSH +If shell completion is not already enabled in your environment you will need to enable it. You can execute the following once: +`echo "autoload -U compinit; compinit" >> ~/.zshrc` + +To make it available in all your zsh sessions run: +`podman completion zsh -f "${fpath[1]}/_podman"` + +Once you reload the shell the autocompletion should be working. + + +### FISH +To load the completion script into your current session run: +`podman completion fish | source` + +To make it available in all your fish sessions run: +`podman completion fish -f ~/.config/fish/completions/podman.fish` + + +## SEE ALSO +[podman(1)](podman.1.md) diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 993f285ed..1954ca2aa 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -168,7 +168,7 @@ podman --remote flag, only the global options `--url`, `--identity`, `--log-leve Connection information can also be managed using the containers.conf file. -## Exit Status +## Exit Codes The exit code from `podman` gives information about why the container failed to run or why it exited. When `podman` commands exit with a non-zero code, @@ -205,6 +205,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-auto-update(1)](podman-auto-update.1.md) | Auto update containers according to their auto-update policy | | [podman-build(1)](podman-build.1.md) | Build a container image using a Containerfile. | | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. | +| [podman-completion(1)](podman-completion.1.md) | Generate shell completion scripts | | [podman-container(1)](podman-container.1.md) | Manage containers. | | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. | | [podman-create(1)](podman-create.1.md) | Create a new container. | @@ -256,7 +257,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-volume(1)](podman-volume.1.md) | Simple management tool for volumes. | | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | -## FILES +## CONFIGURATION FILES **containers.conf** (`/usr/share/containers/containers.conf`, `/etc/containers/containers.conf`, `$HOME/.config/containers/containers.conf`) @@ -10,10 +10,10 @@ require ( github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/containernetworking/cni v0.8.0 github.com/containernetworking/plugins v0.8.7 - github.com/containers/buildah v1.17.0 + github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2 github.com/containers/common v0.27.0 github.com/containers/conmon v2.0.20+incompatible - github.com/containers/image/v5 v5.7.0 + github.com/containers/image/v5 v5.8.0 github.com/containers/psgo v1.5.1 github.com/containers/storage v1.23.9 github.com/coreos/go-systemd/v22 v22.1.0 @@ -60,6 +60,7 @@ require ( github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b github.com/vishvananda/netlink v1.1.0 go.etcd.io/bbolt v1.3.5 + go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 @@ -74,3 +75,5 @@ require ( ) replace github.com/cri-o/ocicni => github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df + +replace github.com/spf13/cobra => github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706 @@ -20,6 +20,8 @@ github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706 h1:KcMtguD/NlxB4c08lzc91o5by51Sf+Ec5+1Yv9Wqvbk= +github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.15-0.20200113171025-3fe6c5262873 h1:93nQ7k53GjoMQ07HVP8g6Zj1fQZDDj7Xy2VkNNtvX8o= @@ -57,6 +59,12 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b h1:T4nWG1TXIxeor8mAu5bFguPJgSIGhZqv/f0z55KCrJM= github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -83,15 +91,16 @@ github.com/containernetworking/cni v0.8.0 h1:BT9lpgGoH4jw3lFC7Odz2prU5ruiYKcgAjM github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.7 h1:bU7QieuAp+sACI2vCzESJ3FoT860urYP+lThyZkb/2M= github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0= -github.com/containers/buildah v1.17.0 h1:oaBIxKtW4kJ06vj4l0C9MZfFVapksf6F4qdQGOvZ2J4= -github.com/containers/buildah v1.17.0/go.mod h1:E6nOiMnF3uCAY3wAQK5lPR6w89SRp8iyIkjUfDKW+Eg= -github.com/containers/common v0.26.2/go.mod h1:igUeog5hx8rYhJk67rG6rGAh3zEcf0Uxuzm9KpXzo2E= +github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2 h1:sYOJ4xbCJTQEhjQax649sE+iy8ZohxmLGP8pCTrnypY= +github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2/go.mod h1:+GBrGojiBt2/IXxKYMCVD02kLIxfe5KYMvCwBjhJkFk= +github.com/containers/common v0.26.3/go.mod h1:hJWZIlrl5MsE2ELNRa+MPp6I1kPbXHauuj0Ym4BsLG4= github.com/containers/common v0.27.0 h1:+QlYEOitVYtU9/x8xebRgxdGqt4sLaIqV6MBOns+zLk= github.com/containers/common v0.27.0/go.mod h1:ZTswJJfu4aGF6Anyi2yON8Getda9NDYcdIzurOEHHXI= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= -github.com/containers/image/v5 v5.7.0 h1:fiTC8/Xbr+zEP6njGTZtPW/3UD7MC93nC9DbUoWdxkA= github.com/containers/image/v5 v5.7.0/go.mod h1:8aOy+YaItukxghRORkvhq5ibWttHErzDLy6egrKfKos= +github.com/containers/image/v5 v5.8.0 h1:B3FGHi0bdGXgg698kBIGOlHCXN5n+scJr6/5354GOPU= +github.com/containers/image/v5 v5.8.0/go.mod h1:jKxdRtyIDumVa56hdsZvV+gwx4zB50hRou6pIuCWLkg= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c= @@ -99,7 +108,6 @@ github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQ github.com/containers/psgo v1.5.1 h1:MQNb7FLbXqBdqz6u4lI2QWizVz4RSTzs1+Nk9XT1iVA= github.com/containers/psgo v1.5.1/go.mod h1:2ubh0SsreMZjSXW1Hif58JrEcFudQyIy9EzPUWfawVU= github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBDb5f8P5CAxJY= -github.com/containers/storage v1.23.7 h1:43ImvG/npvQSZXRjaudVvKISIuZSfI6qvtSNQQSGO/A= github.com/containers/storage v1.23.7/go.mod h1:cUT2zHjtx+WlVri30obWmM2gpqpi8jfPsmIzP1TVpEI= github.com/containers/storage v1.23.9 h1:qbgnTp76pLSyW3vYwY5GH4vk5cHYVXFJ+CsUEBp9TMw= github.com/containers/storage v1.23.9/go.mod h1:3b2ktpB6pw53SEeIoFfO0sQfP9+IoJJKPq5iJk74gxE= @@ -301,17 +309,19 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE= github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -320,10 +330,17 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= +github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -485,9 +502,6 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -552,8 +566,9 @@ go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvS go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -633,6 +648,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/hack/podman-commands.sh b/hack/podman-commands.sh index fd4ff2501..0455287d4 100755 --- a/hack/podman-commands.sh +++ b/hack/podman-commands.sh @@ -25,6 +25,12 @@ function podman_commands() { $PODMAN help "$@" |\ awk '/^Available Commands:/{ok=1;next}/^Options:/{ok=0}ok { print $1 }' |\ grep . + + # Special case: podman-completion is a hidden command + # it does not show in podman help so add it here + if [[ -z "$@" ]]; then + echo "completion" + fi } # Read a list of subcommands from a command's metadoc diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index c5447c5d6..55c8b6582 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -181,6 +181,9 @@ sub xref_by_man { next if $k eq 'varlink'; next if "@subcommand" eq 'system' && $k eq 'service'; + # Special case: podman completion is a hidden command + next if $k eq 'completion'; + warn "$ME: podman @subcommand: $k in $man, but not --help\n"; ++$Errs; } diff --git a/libpod/image/image.go b/libpod/image/image.go index 301954703..cecd64eb7 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -24,6 +24,7 @@ import ( "github.com/containers/image/v5/manifest" ociarchive "github.com/containers/image/v5/oci/archive" "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/tarball" "github.com/containers/image/v5/transports" @@ -164,7 +165,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile } imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label) if err != nil { - return nil, errors.Wrapf(err, "unable to pull %s", name) + return nil, err } newImage, err := ir.NewFromLocal(imageName[0]) @@ -318,10 +319,8 @@ func (ir *Runtime) LoadAllImagesFromDockerArchive(ctx context.Context, fileName } goal := pullGoal{ - pullAllPairs: true, - usedSearchRegistries: false, - refPairs: refPairs, - searchedRegistries: nil, + pullAllPairs: true, + refPairs: refPairs, } defer goal.cleanUp() @@ -456,22 +455,19 @@ func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, erro return "", nil, errors.Wrapf(ErrNoSuchImage, imageError) } - // "Short-name image", so let's try out certain prefixes: - // 1) DefaultLocalRegistry (i.e., "localhost/) - // 2) Unqualified-search registries from registries.conf - unqualifiedSearchRegistries, err := registries.GetRegistries() + sys := &types.SystemContext{ + SystemRegistriesConfPath: registries.SystemRegistriesConfPath(), + } + + candidates, err := shortnames.ResolveLocally(sys, inputName) if err != nil { return "", nil, err } - for _, candidate := range append([]string{DefaultLocalRegistry}, unqualifiedSearchRegistries...) { - ref, err := decomposedImage.referenceWithRegistry(candidate) - if err != nil { - return "", nil, err - } - img, err := ir.store.Image(reference.TagNameOnly(ref).String()) + for _, candidate := range candidates { + img, err := ir.store.Image(candidate.String()) if err == nil { - return ref.String(), img, nil + return candidate.String(), img, nil } } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 65acdf427..2a2d16252 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "os" "path/filepath" "strings" @@ -15,13 +16,14 @@ import ( dockerarchive "github.com/containers/image/v5/docker/archive" ociarchive "github.com/containers/image/v5/oci/archive" oci "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/events" + "github.com/containers/podman/v2/pkg/errorhandling" "github.com/containers/podman/v2/pkg/registries" - "github.com/hashicorp/go-multierror" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -56,9 +58,10 @@ var ( // pullRefPair records a pair of prepared image references to pull. type pullRefPair struct { - image string - srcRef types.ImageReference - dstRef types.ImageReference + image string + srcRef types.ImageReference + dstRef types.ImageReference + resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull } // cleanUpFunc is a function prototype for clean-up functions. @@ -66,11 +69,11 @@ type cleanUpFunc func() error // pullGoal represents the prepared image references and decided behavior to be executed by imagePull type pullGoal struct { - refPairs []pullRefPair - pullAllPairs bool // Pull all refPairs instead of stopping on first success. - usedSearchRegistries bool // refPairs construction has depended on registries.GetRegistries() - searchedRegistries []string // The list of search registries used; set only if usedSearchRegistries - cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader) + refPairs []pullRefPair + pullAllPairs bool // Pull all refPairs instead of stopping on first success. + cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader) + shortName string // Set when pulling a short name + resolved *shortnames.Resolved // Set when pulling a short name } // cleanUp invokes all cleanUpFuncs. Certain resources may not be available @@ -86,10 +89,8 @@ func (p *pullGoal) cleanUp() { // singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair. func singlePullRefPairGoal(rp pullRefPair) *pullGoal { return &pullGoal{ - refPairs: []pullRefPair{rp}, - pullAllPairs: false, // Does not really make a difference. - usedSearchRegistries: false, - searchedRegistries: nil, + refPairs: []pullRefPair{rp}, + pullAllPairs: false, // Does not really make a difference. } } @@ -193,11 +194,9 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types. } return &pullGoal{ - pullAllPairs: true, - usedSearchRegistries: false, - refPairs: pairs, - searchedRegistries: nil, - cleanUpFuncs: []cleanUpFunc{reader.Close}, + pullAllPairs: true, + refPairs: pairs, + cleanUpFuncs: []cleanUpFunc{reader.Close}, }, nil case OCIArchive: @@ -267,7 +266,7 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s if srcTransport != nil && srcTransport.Name() != DockerTransport { return nil, err } - goal, err = ir.pullGoalFromPossiblyUnqualifiedName(inputName) + goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName) if err != nil { return nil, errors.Wrap(err, "error getting default registries to try") } @@ -325,7 +324,7 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa var ( images []string - pullErrors *multierror.Error + pullErrors []error ) for _, imageInfo := range goal.refPairs { @@ -348,12 +347,17 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) return err }, retryOptions); err != nil { - pullErrors = multierror.Append(pullErrors, err) + pullErrors = append(pullErrors, err) logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) if writer != nil { _, _ = io.WriteString(writer, cleanErrorMessage(err)) } } else { + if imageInfo.resolvedShortname != nil { + if err := imageInfo.resolvedShortname.Record(); err != nil { + logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err) + } + } if !goal.pullAllPairs { ir.newImageEvent(events.Pull, "") return []string{imageInfo.image}, nil @@ -361,68 +365,75 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa images = append(images, imageInfo.image) } } - // If no image was found, we should handle. Lets be nicer to the user and see if we can figure out why. + // If no image was found, we should handle. Lets be nicer to the user + // and see if we can figure out why. if len(images) == 0 { - if goal.usedSearchRegistries && len(goal.searchedRegistries) == 0 { - return nil, errors.Errorf("image name provided is a short name and no search registries are defined in the registries config file.") - } - // If the image passed in was fully-qualified, we will have 1 refpair. Bc the image is fq'd, we don't need to yap about registries. - if !goal.usedSearchRegistries { - if pullErrors != nil && len(pullErrors.Errors) > 0 { // this should always be true - return nil, pullErrors.Errors[0] - } - return nil, errors.Errorf("unable to pull image, or you do not have pull access") + if goal.resolved != nil { + return nil, goal.resolved.FormatPullErrors(pullErrors) } - return nil, errors.Cause(pullErrors) - } - if len(images) > 0 { - ir.newImageEvent(events.Pull, images[0]) + return nil, errorhandling.JoinErrors(pullErrors) } + + ir.newImageEvent(events.Pull, images[0]) return images, nil } +// getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment +// variable. If it's "on", return `nil` to use the defaults from +// containers/image and the registries.conf files on the system. If it's +// "off", empty or unset, return types.ShortNameModeDisabled to turn off +// short-name aliasing by default. +// +// TODO: remove this function once we want to default to short-name aliasing. +func getShortNameMode() *types.ShortNameMode { + env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING") + if strings.ToLower(env) == "on" { + return nil // default to whatever registries.conf and c/image decide + } + mode := types.ShortNameModeDisabled + return &mode +} + // pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible // image references to try pulling in combination with the registries.conf file as well -func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullGoal, error) { - decomposedImage, err := decompose(inputName) +func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) { + if sys == nil { + sys = &types.SystemContext{} + } + sys.ShortNameMode = getShortNameMode() + + resolved, err := shortnames.Resolve(sys, inputName) if err != nil { return nil, err } - if decomposedImage.hasRegistry { - srcRef, err := docker.ParseReference("//" + inputName) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse '%s'", inputName) + if desc := resolved.Description(); len(desc) > 0 { + logrus.Debug(desc) + if writer != nil { + if _, err := writer.Write([]byte(desc + "\n")); err != nil { + return nil, err + } } - return ir.getSinglePullRefPairGoal(srcRef, inputName) } - searchRegistries, err := registries.GetRegistries() - if err != nil { - return nil, err - } - refPairs := make([]pullRefPair, 0, len(searchRegistries)) - for _, registry := range searchRegistries { - ref, err := decomposedImage.referenceWithRegistry(registry) + refPairs := []pullRefPair{} + for i, candidate := range resolved.PullCandidates { + srcRef, err := docker.NewReference(candidate.Value) if err != nil { return nil, err } - imageName := ref.String() - srcRef, err := docker.ParseReference("//" + imageName) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse '%s'", imageName) - } - ps, err := ir.getPullRefPair(srcRef, imageName) + ps, err := ir.getPullRefPair(srcRef, candidate.Value.String()) if err != nil { return nil, err } + ps.resolvedShortname = &resolved.PullCandidates[i] refPairs = append(refPairs, ps) } return &pullGoal{ - refPairs: refPairs, - pullAllPairs: false, - usedSearchRegistries: true, - searchedRegistries: searchRegistries, + refPairs: refPairs, + pullAllPairs: false, + shortName: inputName, + resolved: resolved, }, nil } diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go index 6cb80e8b5..2e1464ad3 100644 --- a/libpod/image/pull_test.go +++ b/libpod/image/pull_test.go @@ -278,15 +278,11 @@ func TestPullGoalFromImageReference(t *testing.T) { assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) } assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName) - assert.False(t, res.usedSearchRegistries, c.srcName) - assert.Nil(t, res.searchedRegistries, c.srcName) } } } -const registriesConfWithSearch = `[registries.search] -registries = ['example.com', 'docker.io'] -` +const registriesConfWithSearch = `unqualified-search-registries = ['example.com', 'docker.io']` func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" @@ -303,69 +299,58 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { ir, cleanup := newTestRuntime(t) defer cleanup() - // Environment is per-process, so this looks very unsafe; actually it seems fine because tests are not - // run in parallel unless they opt in by calling t.Parallel(). So don’t do that. - oldRCP, hasRCP := os.LookupEnv("REGISTRIES_CONFIG_PATH") - defer func() { - if hasRCP { - os.Setenv("REGISTRIES_CONFIG_PATH", oldRCP) - } else { - os.Unsetenv("REGISTRIES_CONFIG_PATH") - } - }() - os.Setenv("REGISTRIES_CONFIG_PATH", registriesConf.Name()) + sc := GetSystemContext("", "", false) + + aliasesConf, err := ioutil.TempFile("", "short-name-aliases.conf") + require.NoError(t, err) + defer aliasesConf.Close() + defer os.Remove(aliasesConf.Name()) + sc.UserShortNameAliasConfPath = aliasesConf.Name() + sc.SystemRegistriesConfPath = registriesConf.Name() for _, c := range []struct { - input string - expected []pullRefStrings - expectedUsedSearchRegistries bool + input string + expected []pullRefStrings }{ - {"#", nil, false}, // Clearly invalid. + {"#", nil}, // Clearly invalid. { // Fully-explicit docker.io, name-only. "docker.io/library/busybox", // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, - false, + []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, }, { // docker.io with implied /library/, name-only. "docker.io/busybox", // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, - false, + []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, }, { // Qualified example.com, name-only. "example.com/ns/busybox", - []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}}, - false, + []pullRefStrings{{"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}}, }, { // Qualified example.com, name:tag. "example.com/ns/busybox:notlatest", []pullRefStrings{{"example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}}, - false, }, { // Qualified example.com, name@digest. "example.com/ns/busybox" + digestSuffix, []pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}}, - false, }, // Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. - {"example.com/ns/busybox:notlatest" + digestSuffix, nil, false}, + {"example.com/ns/busybox:notlatest" + digestSuffix, nil}, { // Unqualified, single-name, name-only "busybox", []pullRefStrings{ - {"example.com/busybox", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, + {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}, + {"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}, }, - true, }, { // Unqualified, namespaced, name-only "ns/busybox", []pullRefStrings{ - {"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, + {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, }, - true, }, { // Unqualified, name:tag "busybox:notlatest", @@ -374,7 +359,6 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) {"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"}, }, - true, }, { // Unqualified, name@digest "busybox" + digestSuffix, @@ -383,29 +367,22 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) {"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix}, }, - true, }, // Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. - {"busybox:notlatest" + digestSuffix, nil, false}, + {"busybox:notlatest" + digestSuffix, nil}, } { - res, err := ir.pullGoalFromPossiblyUnqualifiedName(c.input) + res, err := ir.pullGoalFromPossiblyUnqualifiedName(sc, nil, c.input) if len(c.expected) == 0 { assert.Error(t, err, c.input) } else { assert.NoError(t, err, c.input) for i, e := range c.expected { - testDescription := fmt.Sprintf("%s #%d", c.input, i) + testDescription := fmt.Sprintf("%s #%d (%v)", c.input, i, res.refPairs) assert.Equal(t, e.image, res.refPairs[i].image, testDescription) assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription) assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) } assert.False(t, res.pullAllPairs, c.input) - assert.Equal(t, c.expectedUsedSearchRegistries, res.usedSearchRegistries, c.input) - if !c.expectedUsedSearchRegistries { - assert.Nil(t, res.searchedRegistries, c.input) - } else { - assert.Equal(t, []string{"example.com", "docker.io"}, res.searchedRegistries, c.input) - } } } } diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 8011c0a04..abbb6d2c0 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -28,17 +28,17 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { // FYI scope and version are currently unused but are described by the API // Leaving this for if/when we have to enable these - //query := struct { + // query := struct { // scope string // verbose bool - //}{ + // }{ // // override any golang type defaults - //} - //decoder := r.Context().Value("decoder").(*schema.Decoder) - //if err := decoder.Decode(&query, r.URL.Query()); err != nil { + // } + // decoder := r.Context().Value("decoder").(*schema.Decoder) + // if err := decoder.Decode(&query, r.URL.Query()); err != nil { // utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) // return - //} + // } config, err := runtime.GetConfig() if err != nil { utils.InternalServerError(w, err) @@ -119,7 +119,7 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw } report := types.NetworkResource{ Name: name, - ID: "", + ID: name, Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert Scope: "", Driver: network.DefaultNetworkDriver, @@ -207,6 +207,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { } reports := make([]*types.NetworkResource, 0, len(netNames)) + logrus.Errorf("netNames: %q", strings.Join(netNames, ", ")) for _, name := range netNames { report, err := getNetworkResourceByName(name, runtime) if err != nil { @@ -276,21 +277,14 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - report := types.NetworkCreate{ - CheckDuplicate: networkCreate.CheckDuplicate, - Driver: networkCreate.Driver, - Scope: networkCreate.Scope, - EnableIPv6: networkCreate.EnableIPv6, - IPAM: networkCreate.IPAM, - Internal: networkCreate.Internal, - Attachable: networkCreate.Attachable, - Ingress: networkCreate.Ingress, - ConfigOnly: networkCreate.ConfigOnly, - ConfigFrom: networkCreate.ConfigFrom, - Options: networkCreate.Options, - Labels: networkCreate.Labels, + + body := struct { + Id string + Warning []string + }{ + Id: name, } - utils.WriteResponse(w, http.StatusOK, report) + utils.WriteResponse(w, http.StatusCreated, body) } func RemoveNetwork(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 1fc4479ff..52a214883 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -11,13 +11,13 @@ import ( // is set to the unit's (unique) name. const EnvVariable = "PODMAN_SYSTEMD_UNIT" -// restartPolicies includes all valid restart policies to be used in a unit +// RestartPolicies includes all valid restart policies to be used in a unit // file. -var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"} +var RestartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"} // validateRestartPolicy checks that the user-provided policy is valid. func validateRestartPolicy(restart string) error { - for _, i := range restartPolicies { + for _, i := range RestartPolicies { if i == restart { return nil } diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py index 0ac4fde75..7192347c7 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -165,11 +165,34 @@ class TestApi(unittest.TestCase): r = requests.get(_url(ctnr("/containers/{}/logs?stdout=true"))) self.assertEqual(r.status_code, 200, r.text) - def test_post_create(self): - self.skipTest("TODO: create request body") - r = requests.post(_url("/containers/create?args=True")) - self.assertEqual(r.status_code, 200, r.text) - json.loads(r.text) + def test_post_create_compat(self): + """Create network and container then connect to network""" + net = requests.post( + PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"} + ) + self.assertEqual(net.status_code, 201, net.text) + + create = requests.post( + PODMAN_URL + "/v1.40/containers/create?name=postCreate", + json={ + "Cmd": ["date"], + "Image": "alpine:latest", + "NetworkDisabled": False, + "NetworkConfig": { + "EndpointConfig": {"TestNetwork": {"Aliases": ["test_post_create"]}} + }, + }, + ) + self.assertEqual(create.status_code, 201, create.text) + payload = json.loads(create.text) + self.assertIsNotNone(payload["Id"]) + + connect = requests.post( + PODMAN_URL + "/v1.40/networks/TestNetwork/connect", + json={"Container": payload["Id"]}, + ) + self.assertEqual(connect.status_code, 200, create.text) + self.assertEqual(connect.text, "OK\n") def test_commit(self): r = requests.post(_url(ctnr("/commit?container={}"))) diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index ea15e2b8d..87db5a126 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -7,6 +7,7 @@ import ( "runtime" "strings" + "github.com/containers/buildah" . "github.com/containers/podman/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -239,4 +240,16 @@ RUN printenv http_proxy` Expect(ok).To(BeTrue()) os.Unsetenv("http_proxy") }) + + It("podman build and check identity", func() { + session := podmanTest.Podman([]string{"build", "-f", "Containerfile.path", "--no-cache", "-t", "test", "build/basicalpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // Verify that OS and Arch are being set + inspect := podmanTest.PodmanNoCache([]string{"image", "inspect", "--format", "{{ index .Config.Labels }}", "test"}) + inspect.WaitWithDefaultTimeout() + data := inspect.OutputToString() + Expect(data).To(ContainSubstring(buildah.Version)) + }) }) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index b42061c20..96eccdc28 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -198,7 +198,7 @@ WORKDIR /test Expect(result.OutputToString()).To(Equal("/test")) }) - It("podman images filter after image", func() { + It("podman images filter since image", func() { podmanTest.RestoreAllArtifacts() rmi := podmanTest.PodmanNoCache([]string{"rmi", "busybox"}) rmi.WaitWithDefaultTimeout() @@ -207,7 +207,7 @@ WORKDIR /test dockerfile := `FROM quay.io/libpod/alpine:latest ` podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") - result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "after=quay.io/libpod/alpine:latest"}) + result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "since=quay.io/libpod/alpine:latest"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray())).To(Equal(0)) diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index 0e10676b9..316b102f4 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -6,6 +6,8 @@ import shutil import subprocess import tempfile +from docker import DockerClient + from test.python.docker import constant @@ -141,16 +143,15 @@ class Podman(object): def tear_down(self): shutil.rmtree(self.anchor_directory, ignore_errors=True) - def restore_image_from_cache(self, client): - img = os.path.join(self.image_cache, constant.ALPINE_TARBALL) - if not os.path.exists(img): - client.pull(constant.ALPINE) - image = client.get_image(constant.ALPINE) - with open(img, mode="wb") as tarball: - for frame in image: + def restore_image_from_cache(self, client: DockerClient): + path = os.path.join(self.image_cache, constant.ALPINE_TARBALL) + if not os.path.exists(path): + img = client.images.pull(constant.ALPINE) + with open(path, mode="wb") as tarball: + for frame in img.save(named=True): tarball.write(frame) else: - self.run("load", "-i", img, check=True) + self.run("load", "-i", path, check=True) def flush_image_cache(self): for f in pathlib.Path(self.image_cache).glob("*.tar"): diff --git a/test/python/docker/common.py b/test/python/docker/common.py index 2828d2d20..e79d64a9b 100644 --- a/test/python/docker/common.py +++ b/test/python/docker/common.py @@ -1,21 +1,23 @@ -from docker import APIClient +from docker import DockerClient from test.python.docker import constant -def run_top_container(client: APIClient): - c = client.create_container( +def run_top_container(client: DockerClient): + c = client.containers.create( constant.ALPINE, command="top", detach=True, tty=True, name="top" ) - client.start(c.get("Id")) - return c.get("Id") + c.start() + return c.id -def remove_all_containers(client: APIClient): - for ctnr in client.containers(quiet=True): - client.remove_container(ctnr, force=True) +def remove_all_containers(client: DockerClient): + for ctnr in client.containers.list(all=True): + ctnr.remove(force=True) -def remove_all_images(client: APIClient): - for image in client.images(quiet=True): - client.remove_image(image, force=True) +def remove_all_images(client: DockerClient): + for img in client.images.list(): + # FIXME should DELETE /images accept the sha256: prefix? + id_ = img.id.removeprefix("sha256:") + client.images.remove(id_, force=True) diff --git a/test/python/docker/test_containers.py b/test/python/docker/test_containers.py index 1c4c9ab53..5fb340fd4 100644 --- a/test/python/docker/test_containers.py +++ b/test/python/docker/test_containers.py @@ -3,7 +3,7 @@ import sys import time import unittest -from docker import APIClient, errors +from docker import DockerClient, errors from test.python.docker import Podman, common, constant @@ -15,7 +15,7 @@ class TestContainers(unittest.TestCase): def setUp(self): super().setUp() - self.client = APIClient(base_url="tcp://127.0.0.1:8080", timeout=15) + self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) TestContainers.podman.restore_image_from_cache(self.client) TestContainers.topContainerId = common.run_top_container(self.client) self.assertIsNotNone(TestContainers.topContainerId) @@ -52,146 +52,115 @@ class TestContainers(unittest.TestCase): TestContainers.podman.tear_down() return super().tearDownClass() - def test_inspect_container(self): - # Inspect bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.inspect_container("dummy") - self.assertEqual(error.exception.response.status_code, 404) + def test_create_container(self): + # Run a container with detach mode + self.client.containers.create(image="alpine", detach=True) + self.assertEqual(len(self.client.containers.list(all=True)), 2) - # Inspect valid container by Id - container = self.client.inspect_container(TestContainers.topContainerId) - self.assertIn("top", container["Name"]) + def test_create_network(self): + net = self.client.networks.create("testNetwork", driver="bridge") + ctnr = self.client.containers.create(image="alpine", detach=True) + net.connect(ctnr) - # Inspect valid container by name - container = self.client.inspect_container("top") - self.assertIn(TestContainers.topContainerId, container["Id"]) + nets = self.client.networks.list(greedy=True) + self.assertGreaterEqual(len(nets), 1) - def test_create_container(self): - # Run a container with detach mode - container = self.client.create_container(image="alpine", detach=True) - self.assertEqual(len(container), 2) + # TODO fix endpoint to include containers + # for n in nets: + # if n.id == "testNetwork": + # self.assertEqual(ctnr.id, n.containers) + # self.assertTrue(False, "testNetwork not found") def test_start_container(self): - # Start bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.start("dummy") - self.assertEqual(error.exception.response.status_code, 404) - # Podman docs says it should give a 304 but returns with no response # # Start a already started container should return 304 - # response = self.client.start(container=TestContainers.topContainerId) + # response = self.client.api.start(container=TestContainers.topContainerId) # self.assertEqual(error.exception.response.status_code, 304) # Create a new container and validate the count - self.client.create_container(image=constant.ALPINE, name="container2") - containers = self.client.containers(quiet=True, all=True) + self.client.containers.create(image=constant.ALPINE, name="container2") + containers = self.client.containers.list(all=True) self.assertEqual(len(containers), 2) def test_stop_container(self): - # Stop bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.stop("dummy") - self.assertEqual(error.exception.response.status_code, 404) - - # Validate the container state - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "running") + top = self.client.containers.get("top") + self.assertEqual(top.status, "running") # Stop a running container and validate the state - self.client.stop(TestContainers.topContainerId) - container = self.client.inspect_container("top") - self.assertIn( - container["State"]["Status"], - "stopped exited", - ) + top.stop() + top.reload() + self.assertIn(top.status, ("stopped", "exited")) def test_restart_container(self): - # Restart bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.restart("dummy") - self.assertEqual(error.exception.response.status_code, 404) - # Validate the container state - self.client.stop(TestContainers.topContainerId) - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "stopped") + top = self.client.containers.get(TestContainers.topContainerId) + top.stop() + top.reload() + self.assertIn(top.status, ("stopped", "exited")) # restart a running container and validate the state - self.client.restart(TestContainers.topContainerId) - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "running") + top.restart() + top.reload() + self.assertEqual(top.status, "running") def test_remove_container(self): - # Remove bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.remove_container("dummy") - self.assertEqual(error.exception.response.status_code, 404) - # Remove container by ID with force - self.client.remove_container(TestContainers.topContainerId, force=True) - containers = self.client.containers() - self.assertEqual(len(containers), 0) + top = self.client.containers.get(TestContainers.topContainerId) + top.remove(force=True) + self.assertEqual(len(self.client.containers.list()), 0) def test_remove_container_without_force(self): # Validate current container count - containers = self.client.containers() - self.assertTrue(len(containers), 1) + self.assertTrue(len(self.client.containers.list()), 1) # Remove running container should throw error + top = self.client.containers.get(TestContainers.topContainerId) with self.assertRaises(errors.APIError) as error: - self.client.remove_container(TestContainers.topContainerId) + top.remove() self.assertEqual(error.exception.response.status_code, 500) - # Remove container by ID with force - self.client.stop(TestContainers.topContainerId) - self.client.remove_container(TestContainers.topContainerId) - containers = self.client.containers() - self.assertEqual(len(containers), 0) + # Remove container by ID without force + top.stop() + top.remove() + self.assertEqual(len(self.client.containers.list()), 0) def test_pause_container(self): - # Pause bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.pause("dummy") - self.assertEqual(error.exception.response.status_code, 404) - # Validate the container state - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "running") + top = self.client.containers.get(TestContainers.topContainerId) + self.assertEqual(top.status, "running") # Pause a running container and validate the state - self.client.pause(container["Id"]) - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "paused") + top.pause() + top.reload() + self.assertEqual(top.status, "paused") def test_pause_stopped_container(self): # Stop the container - self.client.stop(TestContainers.topContainerId) + top = self.client.containers.get(TestContainers.topContainerId) + top.stop() # Pause exited container should trow error with self.assertRaises(errors.APIError) as error: - self.client.pause(TestContainers.topContainerId) + top.pause() self.assertEqual(error.exception.response.status_code, 500) def test_unpause_container(self): - # Unpause bogus container - with self.assertRaises(errors.NotFound) as error: - self.client.unpause("dummy") - self.assertEqual(error.exception.response.status_code, 404) + top = self.client.containers.get(TestContainers.topContainerId) # Validate the container state - self.client.pause(TestContainers.topContainerId) - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "paused") + top.pause() + top.reload() + self.assertEqual(top.status, "paused") # Pause a running container and validate the state - self.client.unpause(TestContainers.topContainerId) - container = self.client.inspect_container("top") - self.assertEqual(container["State"]["Status"], "running") + top.unpause() + top.reload() + self.assertEqual(top.status, "running") def test_list_container(self): # Add container and validate the count - self.client.create_container(image="alpine", detach=True) - containers = self.client.containers(all=True) + self.client.containers.create(image="alpine", detach=True) + containers = self.client.containers.list(all=True) self.assertEqual(len(containers), 2) def test_filters(self): @@ -199,16 +168,18 @@ class TestContainers(unittest.TestCase): # List container with filter by id filters = {"id": TestContainers.topContainerId} - ctnrs = self.client.containers(all=True, filters=filters) + ctnrs = self.client.containers.list(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) # List container with filter by name filters = {"name": "top"} - ctnrs = self.client.containers(all=True, filters=filters) + ctnrs = self.client.containers.list(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) def test_rename_container(self): + top = self.client.containers.get(TestContainers.topContainerId) + # rename bogus container with self.assertRaises(errors.APIError) as error: - self.client.rename(container="dummy", name="newname") + top.rename(name="newname") self.assertEqual(error.exception.response.status_code, 404) diff --git a/test/python/docker/test_images.py b/test/python/docker/test_images.py index f049da96f..7ef3d708b 100644 --- a/test/python/docker/test_images.py +++ b/test/python/docker/test_images.py @@ -5,7 +5,7 @@ import sys import time import unittest -from docker import APIClient, errors +from docker import DockerClient, errors from test.python.docker import Podman, common, constant @@ -16,7 +16,7 @@ class TestImages(unittest.TestCase): def setUp(self): super().setUp() - self.client = APIClient(base_url="tcp://127.0.0.1:8080", timeout=15) + self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) TestImages.podman.restore_image_from_cache(self.client) @@ -51,83 +51,57 @@ class TestImages(unittest.TestCase): TestImages.podman.tear_down() return super().tearDownClass() - def test_inspect_image(self): - """Inspect Image""" - # Check for error with wrong image name - with self.assertRaises(errors.NotFound): - self.client.inspect_image("dummy") - alpine_image = self.client.inspect_image(constant.ALPINE) - self.assertIn(constant.ALPINE, alpine_image["RepoTags"]) - - def test_tag_invalid_image(self): - """Tag Image - - Validates if invalid image name is given a bad response is encountered - """ - with self.assertRaises(errors.NotFound): - self.client.tag("dummy", "demo") - def test_tag_valid_image(self): """Validates if the image is tagged successfully""" - self.client.tag(constant.ALPINE, "demo", constant.ALPINE_SHORTNAME) - alpine_image = self.client.inspect_image(constant.ALPINE) - for x in alpine_image["RepoTags"]: - self.assertIn("alpine", x) + alpine = self.client.images.get(constant.ALPINE) + self.assertTrue(alpine.tag("demo", constant.ALPINE_SHORTNAME)) + + alpine = self.client.images.get(constant.ALPINE) + for t in alpine.tags: + self.assertIn("alpine", t) # @unittest.skip("doesn't work now") def test_retag_valid_image(self): """Validates if name updates when the image is retagged""" - self.client.tag(constant.ALPINE_SHORTNAME, "demo", "rename") - alpine_image = self.client.inspect_image(constant.ALPINE) - self.assertNotIn("demo:test", alpine_image["RepoTags"]) + alpine = self.client.images.get(constant.ALPINE) + self.assertTrue(alpine.tag("demo", "rename")) + + alpine = self.client.images.get(constant.ALPINE) + self.assertNotIn("demo:test", alpine.tags) def test_list_images(self): """List images""" - all_images = self.client.images() - self.assertEqual(len(all_images), 1) + self.assertEqual(len(self.client.images.list()), 1) + # Add more images - self.client.pull(constant.BB) - all_images = self.client.images() - self.assertEqual(len(all_images), 2) + self.client.images.pull(constant.BB) + self.assertEqual(len(self.client.images.list()), 2) # List images with filter - filters = {"reference": "alpine"} - all_images = self.client.images(filters=filters) - self.assertEqual(len(all_images), 1) + self.assertEqual( + len(self.client.images.list(filters={"reference": "alpine"})), 1 + ) def test_search_image(self): """Search for image""" - response = self.client.search("libpod/alpine") - for i in response: - self.assertIn("quay.io/libpod/alpine", i["Name"]) + for r in self.client.images.search("libpod/alpine"): + self.assertIn("quay.io/libpod/alpine", r["Name"]) def test_remove_image(self): """Remove image""" # Check for error with wrong image name with self.assertRaises(errors.NotFound): - self.client.remove_image("dummy") - all_images = self.client.images() - self.assertEqual(len(all_images), 1) + self.client.images.remove("dummy") + self.assertEqual(len(self.client.images.list()), 1) - alpine_image = self.client.inspect_image(constant.ALPINE) - self.client.remove_image(alpine_image["Id"]) - all_images = self.client.images() - self.assertEqual(len(all_images), 0) + self.client.images.remove(constant.ALPINE) + self.assertEqual(len(self.client.images.list()), 0) def test_image_history(self): """Image history""" - # Check for error with wrong image name - with self.assertRaises(errors.NotFound): - self.client.history("dummy") - - # NOTE: history() has incorrect return type hint - history = self.client.history(constant.ALPINE) - alpine_image = self.client.inspect_image(constant.ALPINE) - image_id = ( - alpine_image["Id"][7:] - if alpine_image["Id"].startswith("sha256:") - else alpine_image["Id"] - ) + img = self.client.images.get(constant.ALPINE) + history = img.history() + image_id = img.id[7:] if img.id.startswith("sha256:") else img.id found = False for change in history: @@ -137,31 +111,34 @@ class TestImages(unittest.TestCase): def test_get_image_exists_not(self): """Negative test for get image""" with self.assertRaises(errors.NotFound): - response = self.client.get_image("image_does_not_exists") + response = self.client.images.get("image_does_not_exists") collections.deque(response) - def test_export_image(self): + def test_save_image(self): """Export Image""" - self.client.pull(constant.BB) - image = self.client.get_image(constant.BB) + image = self.client.images.pull(constant.BB) file = os.path.join(TestImages.podman.image_cache, "busybox.tar") with open(file, mode="wb") as tarball: - for frame in image: + for frame in image.save(named=True): tarball.write(frame) sz = os.path.getsize(file) self.assertGreater(sz, 0) - def test_import_image(self): + def test_load_image(self): """Import|Load Image""" - all_images = self.client.images() - self.assertEqual(len(all_images), 1) + self.assertEqual(len(self.client.images.list()), 1) + + image = self.client.images.pull(constant.BB) + file = os.path.join(TestImages.podman.image_cache, "busybox.tar") + with open(file, mode="wb") as tarball: + for frame in image.save(): + tarball.write(frame) - file = os.path.join(TestImages.podman.image_cache, constant.ALPINE_TARBALL) - self.client.import_image_from_file(filename=file) + with open(file, mode="rb") as saved: + _ = self.client.images.load(saved) - all_images = self.client.images() - self.assertEqual(len(all_images), 2) + self.assertEqual(len(self.client.images.list()), 2) if __name__ == "__main__": diff --git a/test/python/docker/test_system.py b/test/python/docker/test_system.py index f911baee4..46b90e5f6 100644 --- a/test/python/docker/test_system.py +++ b/test/python/docker/test_system.py @@ -3,7 +3,7 @@ import sys import time import unittest -from docker import APIClient +from docker import DockerClient from test.python.docker import Podman, common, constant @@ -15,7 +15,7 @@ class TestSystem(unittest.TestCase): def setUp(self): super().setUp() - self.client = APIClient(base_url="tcp://127.0.0.1:8080", timeout=15) + self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) TestSystem.podman.restore_image_from_cache(self.client) TestSystem.topContainerId = common.run_top_container(self.client) @@ -58,9 +58,10 @@ class TestSystem(unittest.TestCase): def test_info_container_details(self): info = self.client.info() self.assertEqual(info["Containers"], 1) - self.client.create_container(image=constant.ALPINE) + self.client.containers.create(image=constant.ALPINE) info = self.client.info() self.assertEqual(info["Containers"], 2) def test_version(self): - self.assertIsNotNone(self.client.version()) + version = self.client.version() + self.assertIsNotNone(version["Platform"]["Name"]) diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 0741357ed..064cad965 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -221,6 +221,11 @@ EOF run_podman run --rm build_test pwd is "$output" "$workdir" "pwd command in container" + # Determine buildah version, so we can confirm it gets into Labels + run_podman info --format '{{ .Host.BuildahVersion }}' + is "$output" "[1-9][0-9.-]\+" ".Host.BuildahVersion is reasonable" + buildah_version=$output + # Confirm that 'podman inspect' shows the expected values # FIXME: can we rely on .Env[0] being PATH, and the rest being in order?? run_podman image inspect build_test @@ -239,6 +244,7 @@ Cmd[0] | /bin/mydefaultcmd Cmd[1] | $s_echo WorkingDir | $workdir Labels.$label_name | $label_value +Labels.\"io.buildah.version\" | $buildah_version " parse_table "$tests" | while read field expect; do diff --git a/vendor/github.com/chzyer/readline/.gitignore b/vendor/github.com/chzyer/readline/.gitignore new file mode 100644 index 000000000..a3062beae --- /dev/null +++ b/vendor/github.com/chzyer/readline/.gitignore @@ -0,0 +1 @@ +.vscode/* diff --git a/vendor/github.com/chzyer/readline/.travis.yml b/vendor/github.com/chzyer/readline/.travis.yml new file mode 100644 index 000000000..9c3595543 --- /dev/null +++ b/vendor/github.com/chzyer/readline/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.x +script: + - GOOS=windows go install github.com/chzyer/readline/example/... + - GOOS=linux go install github.com/chzyer/readline/example/... + - GOOS=darwin go install github.com/chzyer/readline/example/... + - go test -race -v diff --git a/vendor/github.com/chzyer/readline/CHANGELOG.md b/vendor/github.com/chzyer/readline/CHANGELOG.md new file mode 100644 index 000000000..14ff5be13 --- /dev/null +++ b/vendor/github.com/chzyer/readline/CHANGELOG.md @@ -0,0 +1,58 @@ +# ChangeLog + +### 1.4 - 2016-07-25 + +* [#60][60] Support dynamic autocompletion +* Fix ANSI parser on Windows +* Fix wrong column width in complete mode on Windows +* Remove dependent package "golang.org/x/crypto/ssh/terminal" + +### 1.3 - 2016-05-09 + +* [#38][38] add SetChildren for prefix completer interface +* [#42][42] improve multiple lines compatibility +* [#43][43] remove sub-package(runes) for gopkg compatibility +* [#46][46] Auto complete with space prefixed line +* [#48][48] support suspend process (ctrl+Z) +* [#49][49] fix bug that check equals with previous command +* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty + +### 1.2 - 2016-03-05 + +* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib) +* [#23][23], support stdin remapping +* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM. +* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines. +* Supports performs even stdin/stdout is not a tty. +* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api. +* [#28][28], fixes the history is not working as expected. +* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)` + +### 1.1 - 2015-11-20 + +* [#12][12] Add support for key `<Delete>`/`<Home>`/`<End>` +* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`. +* Bugs fixed for `PrefixCompleter` +* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience. +* Customable Interrupt/EOF prompt in `Config` +* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices +* Provides a new password user experience(`readline.ReadPasswordEx()`). + +### 1.0 - 2015-10-14 + +* Initial public release. + +[12]: https://github.com/chzyer/readline/pull/12 +[17]: https://github.com/chzyer/readline/pull/17 +[23]: https://github.com/chzyer/readline/pull/23 +[27]: https://github.com/chzyer/readline/pull/27 +[28]: https://github.com/chzyer/readline/pull/28 +[33]: https://github.com/chzyer/readline/pull/33 +[38]: https://github.com/chzyer/readline/pull/38 +[42]: https://github.com/chzyer/readline/pull/42 +[43]: https://github.com/chzyer/readline/pull/43 +[46]: https://github.com/chzyer/readline/pull/46 +[48]: https://github.com/chzyer/readline/pull/48 +[49]: https://github.com/chzyer/readline/pull/49 +[53]: https://github.com/chzyer/readline/pull/53 +[60]: https://github.com/chzyer/readline/pull/60 diff --git a/vendor/github.com/chzyer/readline/LICENSE b/vendor/github.com/chzyer/readline/LICENSE new file mode 100644 index 000000000..c9afab3dc --- /dev/null +++ b/vendor/github.com/chzyer/readline/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Chzyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/chzyer/readline/README.md b/vendor/github.com/chzyer/readline/README.md new file mode 100644 index 000000000..fab974b7f --- /dev/null +++ b/vendor/github.com/chzyer/readline/README.md @@ -0,0 +1,114 @@ +[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) +[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases) +[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline) +[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) +[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors) + +<p align="center"> +<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo.png" /> +<a href="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m" target="_blank"><img src="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m.png" width="654"/></a> +<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" /> +</p> + +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` + +## Guide + +* [Demo](example/readline-demo/readline-demo.go) +* [Shortcut](doc/shortcut.md) + +## Repos using readline + +[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach) +[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto) +[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire) +[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg) +[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql) +[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman) +[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp) +[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell) +[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001) +[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p) + + +## Feedback + +If you have any questions, please submit a github issue and any pull requests is welcomed :) + +* [https://twitter.com/chzyer](https://twitter.com/chzyer) +* [http://weibo.com/2145262190](http://weibo.com/2145262190) + + +## Backers + +Love Readline? Help me keep it alive by donating funds to cover project expenses!<br /> +[[Become a backer](https://opencollective.com/readline#backer)] + +<a href="https://opencollective.com/readline/backer/0/website" target="_blank"><img src="https://opencollective.com/readline/backer/0/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/1/website" target="_blank"><img src="https://opencollective.com/readline/backer/1/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/2/website" target="_blank"><img src="https://opencollective.com/readline/backer/2/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/3/website" target="_blank"><img src="https://opencollective.com/readline/backer/3/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/4/website" target="_blank"><img src="https://opencollective.com/readline/backer/4/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/5/website" target="_blank"><img src="https://opencollective.com/readline/backer/5/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/6/website" target="_blank"><img src="https://opencollective.com/readline/backer/6/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/7/website" target="_blank"><img src="https://opencollective.com/readline/backer/7/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/8/website" target="_blank"><img src="https://opencollective.com/readline/backer/8/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/9/website" target="_blank"><img src="https://opencollective.com/readline/backer/9/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/10/website" target="_blank"><img src="https://opencollective.com/readline/backer/10/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/11/website" target="_blank"><img src="https://opencollective.com/readline/backer/11/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/12/website" target="_blank"><img src="https://opencollective.com/readline/backer/12/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/13/website" target="_blank"><img src="https://opencollective.com/readline/backer/13/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/14/website" target="_blank"><img src="https://opencollective.com/readline/backer/14/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/15/website" target="_blank"><img src="https://opencollective.com/readline/backer/15/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/16/website" target="_blank"><img src="https://opencollective.com/readline/backer/16/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/17/website" target="_blank"><img src="https://opencollective.com/readline/backer/17/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/18/website" target="_blank"><img src="https://opencollective.com/readline/backer/18/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/19/website" target="_blank"><img src="https://opencollective.com/readline/backer/19/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/20/website" target="_blank"><img src="https://opencollective.com/readline/backer/20/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/21/website" target="_blank"><img src="https://opencollective.com/readline/backer/21/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/22/website" target="_blank"><img src="https://opencollective.com/readline/backer/22/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/23/website" target="_blank"><img src="https://opencollective.com/readline/backer/23/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/24/website" target="_blank"><img src="https://opencollective.com/readline/backer/24/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/25/website" target="_blank"><img src="https://opencollective.com/readline/backer/25/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/26/website" target="_blank"><img src="https://opencollective.com/readline/backer/26/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/27/website" target="_blank"><img src="https://opencollective.com/readline/backer/27/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/28/website" target="_blank"><img src="https://opencollective.com/readline/backer/28/avatar.svg"></a> +<a href="https://opencollective.com/readline/backer/29/website" target="_blank"><img src="https://opencollective.com/readline/backer/29/avatar.svg"></a> + + +## Sponsors + +Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] + +<a href="https://opencollective.com/readline/sponsor/0/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/0/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/1/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/1/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/2/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/2/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/3/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/3/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/4/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/4/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/5/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/5/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/6/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/6/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/7/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/7/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/8/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/8/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/9/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/9/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/10/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/10/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/11/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/11/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/12/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/12/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/13/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/13/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/14/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/14/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/15/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/15/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/16/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/16/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/17/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/17/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/18/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/18/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/19/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/19/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/20/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/20/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/21/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/21/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/22/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/22/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/23/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/23/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/24/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/24/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/25/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/25/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/26/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/26/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/27/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/27/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/28/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/28/avatar.svg"></a> +<a href="https://opencollective.com/readline/sponsor/29/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/29/avatar.svg"></a> + diff --git a/vendor/github.com/chzyer/readline/ansi_windows.go b/vendor/github.com/chzyer/readline/ansi_windows.go new file mode 100644 index 000000000..63b908c18 --- /dev/null +++ b/vendor/github.com/chzyer/readline/ansi_windows.go @@ -0,0 +1,249 @@ +// +build windows + +package readline + +import ( + "bufio" + "io" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" +) + +const ( + _ = uint16(0) + COLOR_FBLUE = 0x0001 + COLOR_FGREEN = 0x0002 + COLOR_FRED = 0x0004 + COLOR_FINTENSITY = 0x0008 + + COLOR_BBLUE = 0x0010 + COLOR_BGREEN = 0x0020 + COLOR_BRED = 0x0040 + COLOR_BINTENSITY = 0x0080 + + COMMON_LVB_UNDERSCORE = 0x8000 + COMMON_LVB_BOLD = 0x0007 +) + +var ColorTableFg = []word{ + 0, // 30: Black + COLOR_FRED, // 31: Red + COLOR_FGREEN, // 32: Green + COLOR_FRED | COLOR_FGREEN, // 33: Yellow + COLOR_FBLUE, // 34: Blue + COLOR_FRED | COLOR_FBLUE, // 35: Magenta + COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan + COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White +} + +var ColorTableBg = []word{ + 0, // 40: Black + COLOR_BRED, // 41: Red + COLOR_BGREEN, // 42: Green + COLOR_BRED | COLOR_BGREEN, // 43: Yellow + COLOR_BBLUE, // 44: Blue + COLOR_BRED | COLOR_BBLUE, // 45: Magenta + COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan + COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White +} + +type ANSIWriter struct { + target io.Writer + wg sync.WaitGroup + ctx *ANSIWriterCtx + sync.Mutex +} + +func NewANSIWriter(w io.Writer) *ANSIWriter { + a := &ANSIWriter{ + target: w, + ctx: NewANSIWriterCtx(w), + } + return a +} + +func (a *ANSIWriter) Close() error { + a.wg.Wait() + return nil +} + +type ANSIWriterCtx struct { + isEsc bool + isEscSeq bool + arg []string + target *bufio.Writer + wantFlush bool +} + +func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { + return &ANSIWriterCtx{ + target: bufio.NewWriter(target), + } +} + +func (a *ANSIWriterCtx) Flush() { + a.target.Flush() +} + +func (a *ANSIWriterCtx) process(r rune) bool { + if a.wantFlush { + if r == 0 || r == CharEsc { + a.wantFlush = false + a.target.Flush() + } + } + if a.isEscSeq { + a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) + return true + } + + switch r { + case CharEsc: + a.isEsc = true + case '[': + if a.isEsc { + a.arg = nil + a.isEscSeq = true + a.isEsc = false + break + } + fallthrough + default: + a.target.WriteRune(r) + a.wantFlush = true + } + return true +} + +func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { + arg := *argptr + var err error + + if r >= 'A' && r <= 'D' { + count := short(GetInt(arg, 1)) + info, err := GetConsoleScreenBufferInfo() + if err != nil { + return false + } + switch r { + case 'A': // up + info.dwCursorPosition.y -= count + case 'B': // down + info.dwCursorPosition.y += count + case 'C': // right + info.dwCursorPosition.x += count + case 'D': // left + info.dwCursorPosition.x -= count + } + SetConsoleCursorPosition(&info.dwCursorPosition) + return false + } + + switch r { + case 'J': + killLines() + case 'K': + eraseLine() + case 'm': + color := word(0) + for _, item := range arg { + var c int + c, err = strconv.Atoi(item) + if err != nil { + w.WriteString("[" + strings.Join(arg, ";") + "m") + break + } + if c >= 30 && c < 40 { + color ^= COLOR_FINTENSITY + color |= ColorTableFg[c-30] + } else if c >= 40 && c < 50 { + color ^= COLOR_BINTENSITY + color |= ColorTableBg[c-40] + } else if c == 4 { + color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7] + } else if c == 1 { + color |= COMMON_LVB_BOLD | COLOR_FINTENSITY + } else { // unknown code treat as reset + color = ColorTableFg[7] + } + } + if err != nil { + break + } + kernel.SetConsoleTextAttribute(stdout, uintptr(color)) + case '\007': // set title + case ';': + if len(arg) == 0 || arg[len(arg)-1] != "" { + arg = append(arg, "") + *argptr = arg + } + return true + default: + if len(arg) == 0 { + arg = append(arg, "") + } + arg[len(arg)-1] += string(r) + *argptr = arg + return true + } + *argptr = nil + return false +} + +func (a *ANSIWriter) Write(b []byte) (int, error) { + a.Lock() + defer a.Unlock() + + off := 0 + for len(b) > off { + r, size := utf8.DecodeRune(b[off:]) + if size == 0 { + return off, io.ErrShortWrite + } + off += size + a.ctx.process(r) + } + a.ctx.Flush() + return off, nil +} + +func killLines() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x + size += sbi.dwCursorPosition.x + + var written int + kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} + +func eraseLine() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := sbi.dwSize.x + sbi.dwCursorPosition.x = 0 + var written int + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} diff --git a/vendor/github.com/chzyer/readline/complete.go b/vendor/github.com/chzyer/readline/complete.go new file mode 100644 index 000000000..c08c99414 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete.go @@ -0,0 +1,285 @@ +package readline + +import ( + "bufio" + "bytes" + "fmt" + "io" +) + +type AutoCompleter interface { + // Readline will pass the whole line and current offset to it + // Completer need to pass all the candidates, and how long they shared the same characters in line + // Example: + // [go, git, git-shell, grep] + // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 + // Do("gi", 2) => ["t", "t-shell"], 2 + // Do("git", 3) => ["", "-shell"], 3 + Do(line []rune, pos int) (newLine [][]rune, length int) +} + +type TabCompleter struct{} + +func (t *TabCompleter) Do([]rune, int) ([][]rune, int) { + return [][]rune{[]rune("\t")}, 0 +} + +type opCompleter struct { + w io.Writer + op *Operation + width int + + inCompleteMode bool + inSelectMode bool + candidate [][]rune + candidateSource []rune + candidateOff int + candidateChoise int + candidateColNum int +} + +func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { + return &opCompleter{ + w: w, + op: op, + width: width, + } +} + +func (o *opCompleter) doSelect() { + if len(o.candidate) == 1 { + o.op.buf.WriteRunes(o.candidate[0]) + o.ExitCompleteMode(false) + return + } + o.nextCandidate(1) + o.CompleteRefresh() +} + +func (o *opCompleter) nextCandidate(i int) { + o.candidateChoise += i + o.candidateChoise = o.candidateChoise % len(o.candidate) + if o.candidateChoise < 0 { + o.candidateChoise = len(o.candidate) + o.candidateChoise + } +} + +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } + if o.IsInCompleteSelectMode() { + o.doSelect() + return true + } + + buf := o.op.buf + rs := buf.Runes() + + if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { + o.EnterCompleteSelectMode() + o.doSelect() + return true + } + + o.ExitCompleteSelectMode() + o.candidateSource = rs + newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) + if len(newLines) == 0 { + o.ExitCompleteMode(false) + return true + } + + // only Aggregate candidates in non-complete mode + if !o.IsInCompleteMode() { + if len(newLines) == 1 { + buf.WriteRunes(newLines[0]) + o.ExitCompleteMode(false) + return true + } + + same, size := runes.Aggregate(newLines) + if size > 0 { + buf.WriteRunes(same) + o.ExitCompleteMode(false) + return true + } + } + + o.EnterCompleteMode(offset, newLines) + return true +} + +func (o *opCompleter) IsInCompleteSelectMode() bool { + return o.inSelectMode +} + +func (o *opCompleter) IsInCompleteMode() bool { + return o.inCompleteMode +} + +func (o *opCompleter) HandleCompleteSelect(r rune) bool { + next := true + switch r { + case CharEnter, CharCtrlJ: + next = false + o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise]) + o.ExitCompleteMode(false) + case CharLineStart: + num := o.candidateChoise % o.candidateColNum + o.nextCandidate(-num) + case CharLineEnd: + num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1 + o.candidateChoise += num + if o.candidateChoise >= len(o.candidate) { + o.candidateChoise = len(o.candidate) - 1 + } + case CharBackspace: + o.ExitCompleteSelectMode() + next = false + case CharTab, CharForward: + o.doSelect() + case CharBell, CharInterrupt: + o.ExitCompleteMode(true) + next = false + case CharNext: + tmpChoise := o.candidateChoise + o.candidateColNum + if tmpChoise >= o.getMatrixSize() { + tmpChoise -= o.getMatrixSize() + } else if tmpChoise >= len(o.candidate) { + tmpChoise += o.candidateColNum + tmpChoise -= o.getMatrixSize() + } + o.candidateChoise = tmpChoise + case CharBackward: + o.nextCandidate(-1) + case CharPrev: + tmpChoise := o.candidateChoise - o.candidateColNum + if tmpChoise < 0 { + tmpChoise += o.getMatrixSize() + if tmpChoise >= len(o.candidate) { + tmpChoise -= o.candidateColNum + } + } + o.candidateChoise = tmpChoise + default: + next = false + o.ExitCompleteSelectMode() + } + if next { + o.CompleteRefresh() + return true + } + return false +} + +func (o *opCompleter) getMatrixSize() int { + line := len(o.candidate) / o.candidateColNum + if len(o.candidate)%o.candidateColNum != 0 { + line++ + } + return line * o.candidateColNum +} + +func (o *opCompleter) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opCompleter) CompleteRefresh() { + if !o.inCompleteMode { + return + } + lineCnt := o.op.buf.CursorLineCount() + colWidth := 0 + for _, c := range o.candidate { + w := runes.WidthAll(c) + if w > colWidth { + colWidth = w + } + } + colWidth += o.candidateOff + 1 + same := o.op.buf.RuneSlice(-o.candidateOff) + + // -1 to avoid reach the end of line + width := o.width - 1 + colNum := width / colWidth + if colNum != 0 { + colWidth += (width - (colWidth * colNum)) / colNum + } + + o.candidateColNum = colNum + buf := bufio.NewWriter(o.w) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + + colIdx := 0 + lines := 1 + buf.WriteString("\033[J") + for idx, c := range o.candidate { + inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() + if inSelect { + buf.WriteString("\033[30;47m") + } + buf.WriteString(string(same)) + buf.WriteString(string(c)) + buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same))) + + if inSelect { + buf.WriteString("\033[0m") + } + + colIdx++ + if colIdx == colNum { + buf.WriteString("\n") + lines++ + colIdx = 0 + } + } + + // move back + fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) + fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) + buf.Flush() +} + +func (o *opCompleter) aggCandidate(candidate [][]rune) int { + offset := 0 + for i := 0; i < len(candidate[0]); i++ { + for j := 0; j < len(candidate)-1; j++ { + if i > len(candidate[j]) { + goto aggregate + } + if candidate[j][i] != candidate[j+1][i] { + goto aggregate + } + } + offset = i + } +aggregate: + return offset +} + +func (o *opCompleter) EnterCompleteSelectMode() { + o.inSelectMode = true + o.candidateChoise = -1 + o.CompleteRefresh() +} + +func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) { + o.inCompleteMode = true + o.candidate = candidate + o.candidateOff = offset + o.CompleteRefresh() +} + +func (o *opCompleter) ExitCompleteSelectMode() { + o.inSelectMode = false + o.candidate = nil + o.candidateChoise = -1 + o.candidateOff = -1 + o.candidateSource = nil +} + +func (o *opCompleter) ExitCompleteMode(revent bool) { + o.inCompleteMode = false + o.ExitCompleteSelectMode() +} diff --git a/vendor/github.com/chzyer/readline/complete_helper.go b/vendor/github.com/chzyer/readline/complete_helper.go new file mode 100644 index 000000000..58d724872 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_helper.go @@ -0,0 +1,165 @@ +package readline + +import ( + "bytes" + "strings" +) + +// Caller type for dynamic completion +type DynamicCompleteFunc func(string) []string + +type PrefixCompleterInterface interface { + Print(prefix string, level int, buf *bytes.Buffer) + Do(line []rune, pos int) (newLine [][]rune, length int) + GetName() []rune + GetChildren() []PrefixCompleterInterface + SetChildren(children []PrefixCompleterInterface) +} + +type DynamicPrefixCompleterInterface interface { + PrefixCompleterInterface + IsDynamic() bool + GetDynamicNames(line []rune) [][]rune +} + +type PrefixCompleter struct { + Name []rune + Dynamic bool + Callback DynamicCompleteFunc + Children []PrefixCompleterInterface +} + +func (p *PrefixCompleter) Tree(prefix string) string { + buf := bytes.NewBuffer(nil) + p.Print(prefix, 0, buf) + return buf.String() +} + +func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.GetName())) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.GetName()) + "\n") + level++ + } + for _, ch := range p.GetChildren() { + ch.Print(prefix, level, buf) + } +} + +func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { + Print(p, prefix, level, buf) +} + +func (p *PrefixCompleter) IsDynamic() bool { + return p.Dynamic +} + +func (p *PrefixCompleter) GetName() []rune { + return p.Name +} + +func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { + var names = [][]rune{} + for _, name := range p.Callback(string(line)) { + names = append(names, []rune(name+" ")) + } + return names +} + +func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { + return p.Children +} + +func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) { + p.Children = children +} + +func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter { + return PcItem("", pc...) +} + +func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { + name += " " + return &PrefixCompleter{ + Name: []rune(name), + Dynamic: false, + Children: pc, + } +} + +func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { + return &PrefixCompleter{ + Callback: callback, + Dynamic: true, + Children: pc, + } +} + +func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { + line = runes.TrimSpaceLeft(line[:pos]) + goNext := false + var lineCompleter PrefixCompleterInterface + for _, child := range p.GetChildren() { + childNames := make([][]rune, 1) + + childDynamic, ok := child.(DynamicPrefixCompleterInterface) + if ok && childDynamic.IsDynamic() { + childNames = childDynamic.GetDynamicNames(origLine) + } else { + childNames[0] = child.GetName() + } + + for _, childName := range childNames { + if len(line) >= len(childName) { + if runes.HasPrefix(line, childName) { + if len(line) == len(childName) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, childName) + } + offset = len(childName) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[len(line):]) + offset = len(line) + lineCompleter = child + } + } + } + } + + if len(newLine) != 1 { + return + } + + tmpLine := make([]rune, 0, len(line)) + for i := offset; i < len(line); i++ { + if line[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) + } + + if goNext { + return doInternal(lineCompleter, nil, 0, origLine) + } + return +} diff --git a/vendor/github.com/chzyer/readline/complete_segment.go b/vendor/github.com/chzyer/readline/complete_segment.go new file mode 100644 index 000000000..5ceadd80f --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_segment.go @@ -0,0 +1,82 @@ +package readline + +type SegmentCompleter interface { + // a + // |- a1 + // |--- a11 + // |- a2 + // b + // input: + // DoTree([], 0) [a, b] + // DoTree([a], 1) [a] + // DoTree([a, ], 0) [a1, a2] + // DoTree([a, a], 1) [a1, a2] + // DoTree([a, a1], 2) [a1] + // DoTree([a, a1, ], 0) [a11] + // DoTree([a, a1, a], 1) [a11] + DoSegment([][]rune, int) [][]rune +} + +type dumpSegmentCompleter struct { + f func([][]rune, int) [][]rune +} + +func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { + return d.f(segment, n) +} + +func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { + return &SegmentComplete{&dumpSegmentCompleter{f}} +} + +func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete { + return &SegmentComplete{ + SegmentCompleter: completer, + } +} + +type SegmentComplete struct { + SegmentCompleter +} + +func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { + ret := make([][]rune, 0, len(cands)) + lastSegment := segments[len(segments)-1] + for _, cand := range cands { + if !runes.HasPrefix(cand, lastSegment) { + continue + } + ret = append(ret, cand[len(lastSegment):]) + } + return ret, idx +} + +func SplitSegment(line []rune, pos int) ([][]rune, int) { + segs := [][]rune{} + lastIdx := -1 + line = line[:pos] + pos = 0 + for idx, l := range line { + if l == ' ' { + pos = 0 + segs = append(segs, line[lastIdx+1:idx]) + lastIdx = idx + } else { + pos++ + } + } + segs = append(segs, line[lastIdx+1:]) + return segs, pos +} + +func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { + + segment, idx := SplitSegment(line, pos) + + cands := c.DoSegment(segment, idx) + newLine, offset = RetSegment(segment, cands, idx) + for idx := range newLine { + newLine[idx] = append(newLine[idx], ' ') + } + return newLine, offset +} diff --git a/vendor/github.com/chzyer/readline/history.go b/vendor/github.com/chzyer/readline/history.go new file mode 100644 index 000000000..6b17c464b --- /dev/null +++ b/vendor/github.com/chzyer/readline/history.go @@ -0,0 +1,330 @@ +package readline + +import ( + "bufio" + "container/list" + "fmt" + "os" + "strings" + "sync" +) + +type hisItem struct { + Source []rune + Version int64 + Tmp []rune +} + +func (h *hisItem) Clean() { + h.Source = nil + h.Tmp = nil +} + +type opHistory struct { + cfg *Config + history *list.List + historyVer int64 + current *list.Element + fd *os.File + fdLock sync.Mutex + enable bool +} + +func newOpHistory(cfg *Config) (o *opHistory) { + o = &opHistory{ + cfg: cfg, + history: list.New(), + enable: true, + } + return o +} + +func (o *opHistory) Reset() { + o.history = list.New() + o.current = nil +} + +func (o *opHistory) IsHistoryClosed() bool { + o.fdLock.Lock() + defer o.fdLock.Unlock() + return o.fd.Fd() == ^(uintptr(0)) +} + +func (o *opHistory) Init() { + if o.IsHistoryClosed() { + o.initHistory() + } +} + +func (o *opHistory) initHistory() { + if o.cfg.HistoryFile != "" { + o.historyUpdatePath(o.cfg.HistoryFile) + } +} + +// only called by newOpHistory +func (o *opHistory) historyUpdatePath(path string) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return + } + o.fd = f + r := bufio.NewReader(o.fd) + total := 0 + for ; ; total++ { + line, err := r.ReadString('\n') + if err != nil { + break + } + // ignore the empty line + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + o.Push([]rune(line)) + o.Compact() + } + if total > o.cfg.HistoryLimit { + o.rewriteLocked() + } + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Compact() { + for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { + o.history.Remove(o.history.Front()) + } +} + +func (o *opHistory) Rewrite() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + o.rewriteLocked() +} + +func (o *opHistory) rewriteLocked() { + if o.cfg.HistoryFile == "" { + return + } + + tmpFile := o.cfg.HistoryFile + ".tmp" + fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) + if err != nil { + return + } + + buf := bufio.NewWriter(fd) + for elem := o.history.Front(); elem != nil; elem = elem.Next() { + buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") + } + buf.Flush() + + // replace history file + if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { + fd.Close() + return + } + + if o.fd != nil { + o.fd.Close() + } + // fd is write only, just satisfy what we need. + o.fd = fd +} + +func (o *opHistory) Close() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + if o.fd != nil { + o.fd.Close() + } +} + +func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Prev() { + item := o.showItem(elem.Value) + if isNewSearch { + start += len(rs) + } + if elem == o.current { + if len(item) >= start { + item = item[:start] + } + } + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Next() { + item := o.showItem(elem.Value) + if isNewSearch { + start -= len(rs) + if start < 0 { + start = 0 + } + } + if elem == o.current { + if len(item)-1 >= start { + item = item[start:] + } else { + continue + } + } + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + if elem == o.current { + idx += start + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) showItem(obj interface{}) []rune { + item := obj.(*hisItem) + if item.Version == o.historyVer { + return item.Tmp + } + return item.Source +} + +func (o *opHistory) Prev() []rune { + if o.current == nil { + return nil + } + current := o.current.Prev() + if current == nil { + return nil + } + o.current = current + return runes.Copy(o.showItem(current.Value)) +} + +func (o *opHistory) Next() ([]rune, bool) { + if o.current == nil { + return nil, false + } + current := o.current.Next() + if current == nil { + return nil, false + } + + o.current = current + return runes.Copy(o.showItem(current.Value)), true +} + +// Disable the current history +func (o *opHistory) Disable() { + o.enable = false +} + +// Enable the current history +func (o *opHistory) Enable() { + o.enable = true +} + +func (o *opHistory) debug() { + Debug("-------") + for item := o.history.Front(); item != nil; item = item.Next() { + Debug(fmt.Sprintf("%+v", item.Value)) + } +} + +// save history +func (o *opHistory) New(current []rune) (err error) { + + // history deactivated + if !o.enable { + return nil + } + + current = runes.Copy(current) + + // if just use last command without modify + // just clean lastest history + if back := o.history.Back(); back != nil { + prev := back.Prev() + if prev != nil { + if runes.Equal(current, prev.Value.(*hisItem).Source) { + o.current = o.history.Back() + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + } + + if len(current) == 0 { + o.current = o.history.Back() + if o.current != nil { + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + + if o.current != o.history.Back() { + // move history item to current command + currentItem := o.current.Value.(*hisItem) + // set current to last item + o.current = o.history.Back() + + current = runes.Copy(currentItem.Tmp) + } + + // err only can be a IO error, just report + err = o.Update(current, true) + + // push a new one to commit current command + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Revert() { + o.historyVer++ + o.current = o.history.Back() +} + +func (o *opHistory) Update(s []rune, commit bool) (err error) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + s = runes.Copy(s) + if o.current == nil { + o.Push(s) + o.Compact() + return + } + r := o.current.Value.(*hisItem) + r.Version = o.historyVer + if commit { + r.Source = s + if o.fd != nil { + // just report the error + _, err = o.fd.Write([]byte(string(r.Source) + "\n")) + } + } else { + r.Tmp = append(r.Tmp[:0], s...) + } + o.current.Value = r + o.Compact() + return +} + +func (o *opHistory) Push(s []rune) { + s = runes.Copy(s) + elem := o.history.PushBack(&hisItem{Source: s}) + o.current = elem +} diff --git a/vendor/github.com/chzyer/readline/operation.go b/vendor/github.com/chzyer/readline/operation.go new file mode 100644 index 000000000..4c31624f8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/operation.go @@ -0,0 +1,531 @@ +package readline + +import ( + "errors" + "io" + "sync" +) + +var ( + ErrInterrupt = errors.New("Interrupt") +) + +type InterruptError struct { + Line []rune +} + +func (*InterruptError) Error() string { + return "Interrupted" +} + +type Operation struct { + m sync.Mutex + cfg *Config + t *Terminal + buf *RuneBuffer + outchan chan []rune + errchan chan error + w io.Writer + + history *opHistory + *opSearch + *opCompleter + *opPassword + *opVim +} + +func (o *Operation) SetBuffer(what string) { + o.buf.Set([]rune(what)) +} + +type wrapWriter struct { + r *Operation + t *Terminal + target io.Writer +} + +func (w *wrapWriter) Write(b []byte) (int, error) { + if !w.t.IsReading() { + return w.target.Write(b) + } + + var ( + n int + err error + ) + w.r.buf.Refresh(func() { + n, err = w.target.Write(b) + }) + + if w.r.IsSearchMode() { + w.r.SearchRefresh(-1) + } + if w.r.IsInCompleteMode() { + w.r.CompleteRefresh() + } + return n, err +} + +func NewOperation(t *Terminal, cfg *Config) *Operation { + width := cfg.FuncGetWidth() + op := &Operation{ + t: t, + buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), + outchan: make(chan []rune), + errchan: make(chan error, 1), + } + op.w = op.buf.w + op.SetConfig(cfg) + op.opVim = newVimMode(op) + op.opCompleter = newOpCompleter(op.buf.w, op, width) + op.opPassword = newOpPassword(op) + op.cfg.FuncOnWidthChanged(func() { + newWidth := cfg.FuncGetWidth() + op.opCompleter.OnWidthChange(newWidth) + op.opSearch.OnWidthChange(newWidth) + op.buf.OnWidthChange(newWidth) + }) + go op.ioloop() + return op +} + +func (o *Operation) SetPrompt(s string) { + o.buf.SetPrompt(s) +} + +func (o *Operation) SetMaskRune(r rune) { + o.buf.SetMask(r) +} + +func (o *Operation) GetConfig() *Config { + o.m.Lock() + cfg := *o.cfg + o.m.Unlock() + return &cfg +} + +func (o *Operation) ioloop() { + for { + keepInSearchMode := false + keepInCompleteMode := false + r := o.t.ReadRune() + if o.GetConfig().FuncFilterInputRune != nil { + var process bool + r, process = o.GetConfig().FuncFilterInputRune(r) + if !process { + o.buf.Refresh(nil) // to refresh the line + continue // ignore this rune + } + } + + if r == 0 { // io.EOF + if o.buf.Len() == 0 { + o.buf.Clean() + select { + case o.errchan <- io.EOF: + } + break + } else { + // if stdin got io.EOF and there is something left in buffer, + // let's flush them by sending CharEnter. + // And we will got io.EOF int next loop. + r = CharEnter + } + } + isUpdateHistory := true + + if o.IsInCompleteSelectMode() { + keepInCompleteMode = o.HandleCompleteSelect(r) + if keepInCompleteMode { + continue + } + + o.buf.Refresh(nil) + switch r { + case CharEnter, CharCtrlJ: + o.history.Update(o.buf.Runes(), false) + fallthrough + case CharInterrupt: + o.t.KickRead() + fallthrough + case CharBell: + continue + } + } + + if o.IsEnableVimMode() { + r = o.HandleVim(r, o.t.ReadRune) + if r == 0 { + continue + } + } + + switch r { + case CharBell: + if o.IsSearchMode() { + o.ExitSearchMode(true) + o.buf.Refresh(nil) + } + if o.IsInCompleteMode() { + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + } + case CharTab: + if o.GetConfig().AutoComplete == nil { + o.t.Bell() + break + } + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + + case CharBckSearch: + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharCtrlU: + o.buf.KillFront() + case CharFwdSearch: + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharKill: + o.buf.Kill() + keepInCompleteMode = true + case MetaForward: + o.buf.MoveToNextWord() + case CharTranspose: + o.buf.Transpose() + case MetaBackward: + o.buf.MoveToPrevWord() + case MetaDelete: + o.buf.DeleteWord() + case CharLineStart: + o.buf.MoveToLineStart() + case CharLineEnd: + o.buf.MoveToLineEnd() + case CharBackspace, CharCtrlH: + if o.IsSearchMode() { + o.SearchBackspace() + keepInSearchMode = true + break + } + + if o.buf.Len() == 0 { + o.t.Bell() + break + } + o.buf.Backspace() + if o.IsInCompleteMode() { + o.OnComplete() + } + case CharCtrlZ: + o.buf.Clean() + o.t.SleepToResume() + o.Refresh() + case CharCtrlL: + ClearScreen(o.w) + o.Refresh() + case MetaBackspace, CharCtrlW: + o.buf.BackEscapeWord() + case CharCtrlY: + o.buf.Yank() + case CharEnter, CharCtrlJ: + if o.IsSearchMode() { + o.ExitSearchMode(false) + } + o.buf.MoveToLineEnd() + var data []rune + if !o.GetConfig().UniqueEditLine { + o.buf.WriteRune('\n') + data = o.buf.Reset() + data = data[:len(data)-1] // trim \n + } else { + o.buf.Clean() + data = o.buf.Reset() + } + o.outchan <- data + if !o.GetConfig().DisableAutoSaveHistory { + // ignore IO error + _ = o.history.New(data) + } else { + isUpdateHistory = false + } + case CharBackward: + o.buf.MoveBackward() + case CharForward: + o.buf.MoveForward() + case CharPrev: + buf := o.history.Prev() + if buf != nil { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharNext: + buf, ok := o.history.Next() + if ok { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharDelete: + if o.buf.Len() > 0 || !o.IsNormalMode() { + o.t.KickRead() + if !o.buf.Delete() { + o.t.Bell() + } + break + } + + // treat as EOF + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(o.GetConfig().EOFPrompt + "\n") + } + o.buf.Reset() + isUpdateHistory = false + o.history.Revert() + o.errchan <- io.EOF + if o.GetConfig().UniqueEditLine { + o.buf.Clean() + } + case CharInterrupt: + if o.IsSearchMode() { + o.t.KickRead() + o.ExitSearchMode(true) + break + } + if o.IsInCompleteMode() { + o.t.KickRead() + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + break + } + o.buf.MoveToLineEnd() + o.buf.Refresh(nil) + hint := o.GetConfig().InterruptPrompt + "\n" + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(hint) + } + remain := o.buf.Reset() + if !o.GetConfig().UniqueEditLine { + remain = remain[:len(remain)-len([]rune(hint))] + } + isUpdateHistory = false + o.history.Revert() + o.errchan <- &InterruptError{remain} + default: + if o.IsSearchMode() { + o.SearchChar(r) + keepInSearchMode = true + break + } + o.buf.WriteRune(r) + if o.IsInCompleteMode() { + o.OnComplete() + keepInCompleteMode = true + } + } + + listener := o.GetConfig().Listener + if listener != nil { + newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r) + if ok { + o.buf.SetWithIdx(newPos, newLine) + } + } + + o.m.Lock() + if !keepInSearchMode && o.IsSearchMode() { + o.ExitSearchMode(false) + o.buf.Refresh(nil) + } else if o.IsInCompleteMode() { + if !keepInCompleteMode { + o.ExitCompleteMode(false) + o.Refresh() + } else { + o.buf.Refresh(nil) + o.CompleteRefresh() + } + } + if isUpdateHistory && !o.IsSearchMode() { + // it will cause null history + o.history.Update(o.buf.Runes(), false) + } + o.m.Unlock() + } +} + +func (o *Operation) Stderr() io.Writer { + return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t} +} + +func (o *Operation) Stdout() io.Writer { + return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} +} + +func (o *Operation) String() (string, error) { + r, err := o.Runes() + return string(r), err +} + +func (o *Operation) Runes() ([]rune, error) { + o.t.EnterRawMode() + defer o.t.ExitRawMode() + + listener := o.GetConfig().Listener + if listener != nil { + listener.OnChange(nil, 0, 0) + } + + o.buf.Refresh(nil) // print prompt + o.t.KickRead() + select { + case r := <-o.outchan: + return r, nil + case err := <-o.errchan: + if e, ok := err.(*InterruptError); ok { + return e.Line, ErrInterrupt + } + return nil, err + } +} + +func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { + cfg := o.GenPasswordConfig() + cfg.Prompt = prompt + cfg.Listener = l + return o.PasswordWithConfig(cfg) +} + +func (o *Operation) GenPasswordConfig() *Config { + return o.opPassword.PasswordConfig() +} + +func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { + if err := o.opPassword.EnterPasswordMode(cfg); err != nil { + return nil, err + } + defer o.opPassword.ExitPasswordMode() + return o.Slice() +} + +func (o *Operation) Password(prompt string) ([]byte, error) { + return o.PasswordEx(prompt, nil) +} + +func (o *Operation) SetTitle(t string) { + o.w.Write([]byte("\033[2;" + t + "\007")) +} + +func (o *Operation) Slice() ([]byte, error) { + r, err := o.Runes() + if err != nil { + return nil, err + } + return []byte(string(r)), nil +} + +func (o *Operation) Close() { + o.history.Close() +} + +func (o *Operation) SetHistoryPath(path string) { + if o.history != nil { + o.history.Close() + } + o.cfg.HistoryFile = path + o.history = newOpHistory(o.cfg) +} + +func (o *Operation) IsNormalMode() bool { + return !o.IsInCompleteMode() && !o.IsSearchMode() +} + +func (op *Operation) SetConfig(cfg *Config) (*Config, error) { + op.m.Lock() + defer op.m.Unlock() + if op.cfg == cfg { + return op.cfg, nil + } + if err := cfg.Init(); err != nil { + return op.cfg, err + } + old := op.cfg + op.cfg = cfg + op.SetPrompt(cfg.Prompt) + op.SetMaskRune(cfg.MaskRune) + op.buf.SetConfig(cfg) + width := op.cfg.FuncGetWidth() + + if cfg.opHistory == nil { + op.SetHistoryPath(cfg.HistoryFile) + cfg.opHistory = op.history + cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) + } + op.history = cfg.opHistory + + // SetHistoryPath will close opHistory which already exists + // so if we use it next time, we need to reopen it by `InitHistory()` + op.history.Init() + + if op.cfg.AutoComplete != nil { + op.opCompleter = newOpCompleter(op.buf.w, op, width) + } + + op.opSearch = cfg.opSearch + return old, nil +} + +func (o *Operation) ResetHistory() { + o.history.Reset() +} + +// if err is not nil, it just mean it fail to write to file +// other things goes fine. +func (o *Operation) SaveHistory(content string) error { + return o.history.New([]rune(content)) +} + +func (o *Operation) Refresh() { + if o.t.IsReading() { + o.buf.Refresh(nil) + } +} + +func (o *Operation) Clean() { + o.buf.Clean() +} + +func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { + return &DumpListener{f: f} +} + +type DumpListener struct { + f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + return d.f(line, pos, key) +} + +type Listener interface { + OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +type Painter interface { + Paint(line []rune, pos int) []rune +} + +type defaultPainter struct{} + +func (p *defaultPainter) Paint(line []rune, _ int) []rune { + return line +} diff --git a/vendor/github.com/chzyer/readline/password.go b/vendor/github.com/chzyer/readline/password.go new file mode 100644 index 000000000..414288c2a --- /dev/null +++ b/vendor/github.com/chzyer/readline/password.go @@ -0,0 +1,33 @@ +package readline + +type opPassword struct { + o *Operation + backupCfg *Config +} + +func newOpPassword(o *Operation) *opPassword { + return &opPassword{o: o} +} + +func (o *opPassword) ExitPasswordMode() { + o.o.SetConfig(o.backupCfg) + o.backupCfg = nil +} + +func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) { + o.backupCfg, err = o.o.SetConfig(cfg) + return +} + +func (o *opPassword) PasswordConfig() *Config { + return &Config{ + EnableMask: true, + InterruptPrompt: "\n", + EOFPrompt: "\n", + HistoryLimit: -1, + Painter: &defaultPainter{}, + + Stdout: o.o.cfg.Stdout, + Stderr: o.o.cfg.Stderr, + } +} diff --git a/vendor/github.com/chzyer/readline/rawreader_windows.go b/vendor/github.com/chzyer/readline/rawreader_windows.go new file mode 100644 index 000000000..073ef150a --- /dev/null +++ b/vendor/github.com/chzyer/readline/rawreader_windows.go @@ -0,0 +1,125 @@ +// +build windows + +package readline + +import "unsafe" + +const ( + VK_CANCEL = 0x03 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_ESCAPE = 0x1B + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_DELETE = 0x2E + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 +) + +// RawReader translate input record to ANSI escape sequence. +// To provides same behavior as unix terminal. +type RawReader struct { + ctrlKey bool + altKey bool +} + +func NewRawReader() *RawReader { + r := new(RawReader) + return r +} + +// only process one action in one read +func (r *RawReader) Read(buf []byte) (int, error) { + ir := new(_INPUT_RECORD) + var read int + var err error +next: + err = kernel.ReadConsoleInputW(stdin, + uintptr(unsafe.Pointer(ir)), + 1, + uintptr(unsafe.Pointer(&read)), + ) + if err != nil { + return 0, err + } + if ir.EventType != EVENT_KEY { + goto next + } + ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0])) + if ker.bKeyDown == 0 { // keyup + if r.ctrlKey || r.altKey { + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = false + case VK_MENU: //alt + r.altKey = false + } + } + goto next + } + + if ker.unicodeChar == 0 { + var target rune + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = true + case VK_MENU: //alt + r.altKey = true + case VK_LEFT: + target = CharBackward + case VK_RIGHT: + target = CharForward + case VK_UP: + target = CharPrev + case VK_DOWN: + target = CharNext + } + if target != 0 { + return r.write(buf, target) + } + goto next + } + char := rune(ker.unicodeChar) + if r.ctrlKey { + switch char { + case 'A': + char = CharLineStart + case 'E': + char = CharLineEnd + case 'R': + char = CharBckSearch + case 'S': + char = CharFwdSearch + } + } else if r.altKey { + switch char { + case VK_BACK: + char = CharBackspace + } + return r.writeEsc(buf, char) + } + return r.write(buf, char) +} + +func (r *RawReader) writeEsc(b []byte, char rune) (int, error) { + b[0] = '\033' + n := copy(b[1:], []byte(string(char))) + return n + 1, nil +} + +func (r *RawReader) write(b []byte, char rune) (int, error) { + n := copy(b, []byte(string(char))) + return n, nil +} + +func (r *RawReader) Close() error { + return nil +} diff --git a/vendor/github.com/chzyer/readline/readline.go b/vendor/github.com/chzyer/readline/readline.go new file mode 100644 index 000000000..0e7aca06d --- /dev/null +++ b/vendor/github.com/chzyer/readline/readline.go @@ -0,0 +1,326 @@ +// Readline is a pure go implementation for GNU-Readline kind library. +// +// example: +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() +// +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// println(line) +// } +// +package readline + +import "io" + +type Instance struct { + Config *Config + Terminal *Terminal + Operation *Operation +} + +type Config struct { + // prompt supports ANSI escape sequence, so we can color some characters even in windows + Prompt string + + // readline will persist historys to file where HistoryFile specified + HistoryFile string + // specify the max length of historys, it's 500 by default, set it to -1 to disable history + HistoryLimit int + DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool + + // AutoCompleter will called once user press TAB + AutoComplete AutoCompleter + + // Any key press will pass to Listener + // NOTE: Listener will be triggered by (nil, 0, 0) immediately + Listener Listener + + Painter Painter + + // If VimMode is true, readline will in vim.insert mode by default + VimMode bool + + InterruptPrompt string + EOFPrompt string + + FuncGetWidth func() int + + Stdin io.ReadCloser + StdinWriter io.Writer + Stdout io.Writer + Stderr io.Writer + + EnableMask bool + MaskRune rune + + // erase the editing line after user submited it + // it use in IM usually. + UniqueEditLine bool + + // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) + // -> output = new (translated) rune and true/false if continue with processing this one + FuncFilterInputRune func(rune) (rune, bool) + + // force use interactive even stdout is not a tty + FuncIsTerminal func() bool + FuncMakeRaw func() error + FuncExitRaw func() error + FuncOnWidthChanged func(func()) + ForceUseInteractive bool + + // private fields + inited bool + opHistory *opHistory + opSearch *opSearch +} + +func (c *Config) useInteractive() bool { + if c.ForceUseInteractive { + return true + } + return c.FuncIsTerminal() +} + +func (c *Config) Init() error { + if c.inited { + return nil + } + c.inited = true + if c.Stdin == nil { + c.Stdin = NewCancelableStdin(Stdin) + } + + c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) + + if c.Stdout == nil { + c.Stdout = Stdout + } + if c.Stderr == nil { + c.Stderr = Stderr + } + if c.HistoryLimit == 0 { + c.HistoryLimit = 500 + } + + if c.InterruptPrompt == "" { + c.InterruptPrompt = "^C" + } else if c.InterruptPrompt == "\n" { + c.InterruptPrompt = "" + } + if c.EOFPrompt == "" { + c.EOFPrompt = "^D" + } else if c.EOFPrompt == "\n" { + c.EOFPrompt = "" + } + + if c.AutoComplete == nil { + c.AutoComplete = &TabCompleter{} + } + if c.FuncGetWidth == nil { + c.FuncGetWidth = GetScreenWidth + } + if c.FuncIsTerminal == nil { + c.FuncIsTerminal = DefaultIsTerminal + } + rm := new(RawMode) + if c.FuncMakeRaw == nil { + c.FuncMakeRaw = rm.Enter + } + if c.FuncExitRaw == nil { + c.FuncExitRaw = rm.Exit + } + if c.FuncOnWidthChanged == nil { + c.FuncOnWidthChanged = DefaultOnWidthChanged + } + + return nil +} + +func (c Config) Clone() *Config { + c.opHistory = nil + c.opSearch = nil + return &c +} + +func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { + c.Listener = FuncListener(f) +} + +func (c *Config) SetPainter(p Painter) { + c.Painter = p +} + +func NewEx(cfg *Config) (*Instance, error) { + t, err := NewTerminal(cfg) + if err != nil { + return nil, err + } + rl := t.Readline() + if cfg.Painter == nil { + cfg.Painter = &defaultPainter{} + } + return &Instance{ + Config: cfg, + Terminal: t, + Operation: rl, + }, nil +} + +func New(prompt string) (*Instance, error) { + return NewEx(&Config{Prompt: prompt}) +} + +func (i *Instance) ResetHistory() { + i.Operation.ResetHistory() +} + +func (i *Instance) SetPrompt(s string) { + i.Operation.SetPrompt(s) +} + +func (i *Instance) SetMaskRune(r rune) { + i.Operation.SetMaskRune(r) +} + +// change history persistence in runtime +func (i *Instance) SetHistoryPath(p string) { + i.Operation.SetHistoryPath(p) +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stdout() io.Writer { + return i.Operation.Stdout() +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stderr() io.Writer { + return i.Operation.Stderr() +} + +// switch VimMode in runtime +func (i *Instance) SetVimMode(on bool) { + i.Operation.SetVimMode(on) +} + +func (i *Instance) IsVimMode() bool { + return i.Operation.IsEnableVimMode() +} + +func (i *Instance) GenPasswordConfig() *Config { + return i.Operation.GenPasswordConfig() +} + +// we can generate a config by `i.GenPasswordConfig()` +func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { + return i.Operation.PasswordWithConfig(cfg) +} + +func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { + return i.Operation.PasswordEx(prompt, l) +} + +func (i *Instance) ReadPassword(prompt string) ([]byte, error) { + return i.Operation.Password(prompt) +} + +type Result struct { + Line string + Error error +} + +func (l *Result) CanContinue() bool { + return len(l.Line) != 0 && l.Error == ErrInterrupt +} + +func (l *Result) CanBreak() bool { + return !l.CanContinue() && l.Error != nil +} + +func (i *Instance) Line() *Result { + ret, err := i.Readline() + return &Result{ret, err} +} + +// err is one of (nil, io.EOF, readline.ErrInterrupt) +func (i *Instance) Readline() (string, error) { + return i.Operation.String() +} + +func (i *Instance) ReadlineWithDefault(what string) (string, error) { + i.Operation.SetBuffer(what) + return i.Operation.String() +} + +func (i *Instance) SaveHistory(content string) error { + return i.Operation.SaveHistory(content) +} + +// same as readline +func (i *Instance) ReadSlice() ([]byte, error) { + return i.Operation.Slice() +} + +// we must make sure that call Close() before process exit. +func (i *Instance) Close() error { + if err := i.Terminal.Close(); err != nil { + return err + } + i.Config.Stdin.Close() + i.Operation.Close() + return nil +} +func (i *Instance) Clean() { + i.Operation.Clean() +} + +func (i *Instance) Write(b []byte) (int, error) { + return i.Stdout().Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +// ie : +// i := readline.New() +// i.WriteStdin([]byte("test")) +// _, _= i.Readline() +// +// gives +// +// > test[cursor] +func (i *Instance) WriteStdin(val []byte) (int, error) { + return i.Terminal.WriteStdin(val) +} + +func (i *Instance) SetConfig(cfg *Config) *Config { + if i.Config == cfg { + return cfg + } + old := i.Config + i.Config = cfg + i.Operation.SetConfig(cfg) + i.Terminal.SetConfig(cfg) + return old +} + +func (i *Instance) Refresh() { + i.Operation.Refresh() +} + +// HistoryDisable the save of the commands into the history +func (i *Instance) HistoryDisable() { + i.Operation.history.Disable() +} + +// HistoryEnable the save of the commands into the history (default on) +func (i *Instance) HistoryEnable() { + i.Operation.history.Enable() +} diff --git a/vendor/github.com/chzyer/readline/remote.go b/vendor/github.com/chzyer/readline/remote.go new file mode 100644 index 000000000..74dbf5690 --- /dev/null +++ b/vendor/github.com/chzyer/readline/remote.go @@ -0,0 +1,475 @@ +package readline + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "os" + "sync" + "sync/atomic" +) + +type MsgType int16 + +const ( + T_DATA = MsgType(iota) + T_WIDTH + T_WIDTH_REPORT + T_ISTTY_REPORT + T_RAW + T_ERAW // exit raw + T_EOF +) + +type RemoteSvr struct { + eof int32 + closed int32 + width int32 + reciveChan chan struct{} + writeChan chan *writeCtx + conn net.Conn + isTerminal bool + funcWidthChan func() + stopChan chan struct{} + + dataBufM sync.Mutex + dataBuf bytes.Buffer +} + +type writeReply struct { + n int + err error +} + +type writeCtx struct { + msg *Message + reply chan *writeReply +} + +func newWriteCtx(msg *Message) *writeCtx { + return &writeCtx{ + msg: msg, + reply: make(chan *writeReply), + } +} + +func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) { + rs := &RemoteSvr{ + width: -1, + conn: conn, + writeChan: make(chan *writeCtx), + reciveChan: make(chan struct{}), + stopChan: make(chan struct{}), + } + buf := bufio.NewReader(rs.conn) + + if err := rs.init(buf); err != nil { + return nil, err + } + + go rs.readLoop(buf) + go rs.writeLoop() + return rs, nil +} + +func (r *RemoteSvr) init(buf *bufio.Reader) error { + m, err := ReadMessage(buf) + if err != nil { + return err + } + // receive isTerminal + if m.Type != T_ISTTY_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotIsTerminal(m.Data) + + // receive width + m, err = ReadMessage(buf) + if err != nil { + return err + } + if m.Type != T_WIDTH_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotReportWidth(m.Data) + + return nil +} + +func (r *RemoteSvr) HandleConfig(cfg *Config) { + cfg.Stderr = r + cfg.Stdout = r + cfg.Stdin = r + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncIsTerminal = r.IsTerminal + cfg.FuncMakeRaw = r.EnterRawMode + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncGetWidth = r.GetWidth + cfg.FuncOnWidthChanged = func(f func()) { + r.funcWidthChan = f + } +} + +func (r *RemoteSvr) IsTerminal() bool { + return r.isTerminal +} + +func (r *RemoteSvr) checkEOF() error { + if atomic.LoadInt32(&r.eof) == 1 { + return io.EOF + } + return nil +} + +func (r *RemoteSvr) Read(b []byte) (int, error) { + r.dataBufM.Lock() + n, err := r.dataBuf.Read(b) + r.dataBufM.Unlock() + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + if n == 0 && err == io.EOF { + <-r.reciveChan + r.dataBufM.Lock() + n, err = r.dataBuf.Read(b) + r.dataBufM.Unlock() + } + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + return n, err +} + +func (r *RemoteSvr) writeMsg(m *Message) error { + ctx := newWriteCtx(m) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.err +} + +func (r *RemoteSvr) Write(b []byte) (int, error) { + ctx := newWriteCtx(NewMessage(T_DATA, b)) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.n, reply.err +} + +func (r *RemoteSvr) EnterRawMode() error { + return r.writeMsg(NewMessage(T_RAW, nil)) +} + +func (r *RemoteSvr) ExitRawMode() error { + return r.writeMsg(NewMessage(T_ERAW, nil)) +} + +func (r *RemoteSvr) writeLoop() { + defer r.Close() + +loop: + for { + select { + case ctx, ok := <-r.writeChan: + if !ok { + break + } + n, err := ctx.msg.WriteTo(r.conn) + ctx.reply <- &writeReply{n, err} + case <-r.stopChan: + break loop + } + } +} + +func (r *RemoteSvr) Close() error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + close(r.stopChan) + r.conn.Close() + } + return nil +} + +func (r *RemoteSvr) readLoop(buf *bufio.Reader) { + defer r.Close() + for { + m, err := ReadMessage(buf) + if err != nil { + break + } + switch m.Type { + case T_EOF: + atomic.StoreInt32(&r.eof, 1) + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_DATA: + r.dataBufM.Lock() + r.dataBuf.Write(m.Data) + r.dataBufM.Unlock() + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_WIDTH_REPORT: + r.GotReportWidth(m.Data) + case T_ISTTY_REPORT: + r.GotIsTerminal(m.Data) + } + } +} + +func (r *RemoteSvr) GotIsTerminal(data []byte) { + if binary.BigEndian.Uint16(data) == 0 { + r.isTerminal = false + } else { + r.isTerminal = true + } +} + +func (r *RemoteSvr) GotReportWidth(data []byte) { + atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data))) + if r.funcWidthChan != nil { + r.funcWidthChan() + } +} + +func (r *RemoteSvr) GetWidth() int { + return int(atomic.LoadInt32(&r.width)) +} + +// ----------------------------------------------------------------------------- + +type Message struct { + Type MsgType + Data []byte +} + +func ReadMessage(r io.Reader) (*Message, error) { + m := new(Message) + var length int32 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil { + return nil, err + } + m.Data = make([]byte, int(length)-2) + if _, err := io.ReadFull(r, m.Data); err != nil { + return nil, err + } + return m, nil +} + +func NewMessage(t MsgType, data []byte) *Message { + return &Message{t, data} +} + +func (m *Message) WriteTo(w io.Writer) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4)) + binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2)) + binary.Write(buf, binary.BigEndian, m.Type) + buf.Write(m.Data) + n, err := buf.WriteTo(w) + return int(n), err +} + +// ----------------------------------------------------------------------------- + +type RemoteCli struct { + conn net.Conn + raw RawMode + receiveChan chan struct{} + inited int32 + isTerminal *bool + + data bytes.Buffer + dataM sync.Mutex +} + +func NewRemoteCli(conn net.Conn) (*RemoteCli, error) { + r := &RemoteCli{ + conn: conn, + receiveChan: make(chan struct{}), + } + return r, nil +} + +func (r *RemoteCli) MarkIsTerminal(is bool) { + r.isTerminal = &is +} + +func (r *RemoteCli) init() error { + if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) { + return nil + } + + if err := r.reportIsTerminal(); err != nil { + return err + } + + if err := r.reportWidth(); err != nil { + return err + } + + // register sig for width changed + DefaultOnWidthChanged(func() { + r.reportWidth() + }) + return nil +} + +func (r *RemoteCli) writeMsg(m *Message) error { + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return err +} + +func (r *RemoteCli) Write(b []byte) (int, error) { + m := NewMessage(T_DATA, b) + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return len(b), err +} + +func (r *RemoteCli) reportWidth() error { + screenWidth := GetScreenWidth() + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(screenWidth)) + msg := NewMessage(T_WIDTH_REPORT, data) + + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) reportIsTerminal() error { + var isTerminal bool + if r.isTerminal != nil { + isTerminal = *r.isTerminal + } else { + isTerminal = DefaultIsTerminal() + } + data := make([]byte, 2) + if isTerminal { + binary.BigEndian.PutUint16(data, 1) + } else { + binary.BigEndian.PutUint16(data, 0) + } + msg := NewMessage(T_ISTTY_REPORT, data) + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) readLoop() { + buf := bufio.NewReader(r.conn) + for { + msg, err := ReadMessage(buf) + if err != nil { + break + } + switch msg.Type { + case T_ERAW: + r.raw.Exit() + case T_RAW: + r.raw.Enter() + case T_DATA: + os.Stdout.Write(msg.Data) + } + } +} + +func (r *RemoteCli) ServeBy(source io.Reader) error { + if err := r.init(); err != nil { + return err + } + + go func() { + defer r.Close() + for { + n, _ := io.Copy(r, source) + if n == 0 { + break + } + } + }() + defer r.raw.Exit() + r.readLoop() + return nil +} + +func (r *RemoteCli) Close() { + r.writeMsg(NewMessage(T_EOF, nil)) +} + +func (r *RemoteCli) Serve() error { + return r.ServeBy(os.Stdin) +} + +func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error { + ln, err := net.Listen(n, addr) + if err != nil { + return err + } + if len(onListen) > 0 { + if err := onListen[0](ln); err != nil { + return err + } + } + for { + conn, err := ln.Accept() + if err != nil { + break + } + go func() { + defer conn.Close() + rl, err := HandleConn(*cfg, conn) + if err != nil { + return + } + h(rl) + }() + } + return nil +} + +func HandleConn(cfg Config, conn net.Conn) (*Instance, error) { + r, err := NewRemoteSvr(conn) + if err != nil { + return nil, err + } + r.HandleConfig(&cfg) + + rl, err := NewEx(&cfg) + if err != nil { + return nil, err + } + return rl, nil +} + +func DialRemote(n, addr string) error { + conn, err := net.Dial(n, addr) + if err != nil { + return err + } + defer conn.Close() + + cli, err := NewRemoteCli(conn) + if err != nil { + return err + } + return cli.Serve() +} diff --git a/vendor/github.com/chzyer/readline/runebuf.go b/vendor/github.com/chzyer/readline/runebuf.go new file mode 100644 index 000000000..81d2da50c --- /dev/null +++ b/vendor/github.com/chzyer/readline/runebuf.go @@ -0,0 +1,629 @@ +package readline + +import ( + "bufio" + "bytes" + "io" + "strconv" + "strings" + "sync" +) + +type runeBufferBck struct { + buf []rune + idx int +} + +type RuneBuffer struct { + buf []rune + idx int + prompt []rune + w io.Writer + + hadClean bool + interactive bool + cfg *Config + + width int + + bck *runeBufferBck + + offset string + + lastKill []rune + + sync.Mutex +} + +func (r* RuneBuffer) pushKill(text []rune) { + r.lastKill = append([]rune{}, text...) +} + +func (r *RuneBuffer) OnWidthChange(newWidth int) { + r.Lock() + r.width = newWidth + r.Unlock() +} + +func (r *RuneBuffer) Backup() { + r.Lock() + r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() +} + +func (r *RuneBuffer) Restore() { + r.Refresh(func() { + if r.bck == nil { + return + } + r.buf = r.bck.buf + r.idx = r.bck.idx + }) +} + +func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { + rb := &RuneBuffer{ + w: w, + interactive: cfg.useInteractive(), + cfg: cfg, + width: width, + } + rb.SetPrompt(prompt) + return rb +} + +func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() + r.cfg = cfg + r.interactive = cfg.useInteractive() + r.Unlock() +} + +func (r *RuneBuffer) SetMask(m rune) { + r.Lock() + r.cfg.MaskRune = m + r.Unlock() +} + +func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() + return runes.WidthAll(r.buf[:x]) +} + +func (r *RuneBuffer) PromptLen() int { + r.Lock() + width := r.promptLen() + r.Unlock() + return width +} + +func (r *RuneBuffer) promptLen() int { + return runes.WidthAll(runes.ColorFilter(r.prompt)) +} + +func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + + if i > 0 { + rs := make([]rune, i) + copy(rs, r.buf[r.idx:r.idx+i]) + return rs + } + rs := make([]rune, -i) + copy(rs, r.buf[r.idx+i:r.idx]) + return rs +} + +func (r *RuneBuffer) Runes() []rune { + r.Lock() + newr := make([]rune, len(r.buf)) + copy(newr, r.buf) + r.Unlock() + return newr +} + +func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() + return r.idx +} + +func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() + return len(r.buf) +} + +func (r *RuneBuffer) MoveToLineStart() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx = 0 + }) +} + +func (r *RuneBuffer) MoveBackward() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx-- + }) +} + +func (r *RuneBuffer) WriteString(s string) { + r.WriteRunes([]rune(s)) +} + +func (r *RuneBuffer) WriteRune(s rune) { + r.WriteRunes([]rune{s}) +} + +func (r *RuneBuffer) WriteRunes(s []rune) { + r.Refresh(func() { + tail := append(s, r.buf[r.idx:]...) + r.buf = append(r.buf[:r.idx], tail...) + r.idx += len(s) + }) +} + +func (r *RuneBuffer) MoveForward() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.idx++ + }) +} + +func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() + return r.idx == len(r.buf) +} + +func (r *RuneBuffer) Replace(ch rune) { + r.Refresh(func() { + r.buf[r.idx] = ch + }) +} + +func (r *RuneBuffer) Erase() { + r.Refresh(func() { + r.idx = 0 + r.pushKill(r.buf[:]) + r.buf = r.buf[:0] + }) +} + +func (r *RuneBuffer) Delete() (success bool) { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.pushKill(r.buf[r.idx : r.idx+1]) + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + success = true + }) + return +} + +func (r *RuneBuffer) DeleteWord() { + if r.idx == len(r.buf) { + return + } + init := r.idx + for init < len(r.buf) && IsWordBreak(r.buf[init]) { + init++ + } + for i := init + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[r.idx:i-1]) + r.Refresh(func() { + r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) + }) + return + } + } + r.Kill() +} + +func (r *RuneBuffer) MoveToPrevWord() (success bool) { + r.Refresh(func() { + if r.idx == 0 { + return + } + + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + success = true + return + } + } + r.idx = 0 + success = true + }) + return +} + +func (r *RuneBuffer) KillFront() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + length := len(r.buf) - r.idx + r.pushKill(r.buf[:r.idx]) + copy(r.buf[:length], r.buf[r.idx:]) + r.idx = 0 + r.buf = r.buf[:length] + }) +} + +func (r *RuneBuffer) Kill() { + r.Refresh(func() { + r.pushKill(r.buf[r.idx:]) + r.buf = r.buf[:r.idx] + }) +} + +func (r *RuneBuffer) Transpose() { + r.Refresh(func() { + if len(r.buf) == 1 { + r.idx++ + } + + if len(r.buf) < 2 { + return + } + + if r.idx == 0 { + r.idx = 1 + } else if r.idx >= len(r.buf) { + r.idx = len(r.buf) - 1 + } + r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] + r.idx++ + }) +} + +func (r *RuneBuffer) MoveToNextWord() { + r.Refresh(func() { + for i := r.idx + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + return + } + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) MoveToEndWord() { + r.Refresh(func() { + // already at the end, so do nothing + if r.idx == len(r.buf) { + return + } + // if we are at the end of a word already, go to next + if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { + r.idx++ + } + + // keep going until at the end of a word + for i := r.idx + 1; i < len(r.buf); i++ { + if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { + r.idx = i - 1 + return + } + } + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) BackEscapeWord() { + r.Refresh(func() { + if r.idx == 0 { + return + } + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[i:r.idx]) + r.buf = append(r.buf[:i], r.buf[r.idx:]...) + r.idx = i + return + } + } + + r.buf = r.buf[:0] + r.idx = 0 + }) +} + +func (r *RuneBuffer) Yank() { + if len(r.lastKill) == 0 { + return + } + r.Refresh(func() { + buf := make([]rune, 0, len(r.buf) + len(r.lastKill)) + buf = append(buf, r.buf[:r.idx]...) + buf = append(buf, r.lastKill...) + buf = append(buf, r.buf[r.idx:]...) + r.buf = buf + r.idx += len(r.lastKill) + }) +} + +func (r *RuneBuffer) Backspace() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + r.idx-- + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + }) +} + +func (r *RuneBuffer) MoveToLineEnd() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) LineCount(width int) int { + if width == -1 { + width = r.width + } + return LineCount(width, + runes.WidthAll(r.buf)+r.PromptLen()) +} + +func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { + r.Refresh(func() { + if reverse { + for i := r.idx - 1; i >= 0; i-- { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx++ + } + success = true + return + } + } + return + } + for i := r.idx + 1; i < len(r.buf); i++ { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx-- + } + success = true + return + } + } + }) + return +} + +func (r *RuneBuffer) isInLineEdge() bool { + if isWindows { + return false + } + sp := r.getSplitByLine(r.buf) + return len(sp[len(sp)-1]) == 0 +} + +func (r *RuneBuffer) getSplitByLine(rs []rune) []string { + return SplitByLine(r.promptLen(), r.width, rs) +} + +func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { + if width == 0 { + return 0 + } + sp := r.getSplitByLine(r.buf[:r.idx]) + return len(sp) - 1 +} + +func (r *RuneBuffer) CursorLineCount() int { + return r.LineCount(r.width) - r.IdxLine(r.width) +} + +func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + + if !r.interactive { + if f != nil { + f() + } + return + } + + r.clean() + if f != nil { + f() + } + r.print() +} + +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + +func (r *RuneBuffer) print() { + r.w.Write(r.output()) + r.hadClean = false +} + +func (r *RuneBuffer) output() []byte { + buf := bytes.NewBuffer(nil) + buf.WriteString(string(r.prompt)) + if r.cfg.EnableMask && len(r.buf) > 0 { + buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) + if r.buf[len(r.buf)-1] == '\n' { + buf.Write([]byte{'\n'}) + } else { + buf.Write([]byte(string(r.cfg.MaskRune))) + } + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + + } else { + for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { + if e == '\t' { + buf.WriteString(strings.Repeat(" ", TabWidth)) + } else { + buf.WriteRune(e) + } + } + if r.isInLineEdge() { + buf.Write([]byte(" \b")) + } + } + // cursor position + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + return buf.Bytes() +} + +func (r *RuneBuffer) getBackspaceSequence() []byte { + var sep = map[int]bool{} + + var i int + for { + if i >= runes.WidthAll(r.buf) { + break + } + + if i == 0 { + i -= r.promptLen() + } + i += r.width + + sep[i] = true + } + var buf []byte + for i := len(r.buf); i > r.idx; i-- { + // move input to the left of one + buf = append(buf, '\b') + if sep[i] { + // up one line, go to the start of the line and move cursor right to the end (r.width) + buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) + } + } + + return buf + +} + +func (r *RuneBuffer) Reset() []rune { + ret := runes.Copy(r.buf) + r.buf = r.buf[:0] + r.idx = 0 + return ret +} + +func (r *RuneBuffer) calWidth(m int) int { + if m > 0 { + return runes.WidthAll(r.buf[r.idx : r.idx+m]) + } + return runes.WidthAll(r.buf[r.idx+m : r.idx]) +} + +func (r *RuneBuffer) SetStyle(start, end int, style string) { + if end < start { + panic("end < start") + } + + // goto start + move := start - r.idx + if move > 0 { + r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) + } else { + r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) + } + r.w.Write([]byte("\033[" + style + "m")) + r.w.Write([]byte(string(r.buf[start:end]))) + r.w.Write([]byte("\033[0m")) + // TODO: move back +} + +func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { + r.Refresh(func() { + r.buf = buf + r.idx = idx + }) +} + +func (r *RuneBuffer) Set(buf []rune) { + r.SetWithIdx(len(buf), buf) +} + +func (r *RuneBuffer) SetPrompt(prompt string) { + r.Lock() + r.prompt = []rune(prompt) + r.Unlock() +} + +func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { + buf := bufio.NewWriter(w) + + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) + buf.Write([]byte("\033[J")) + } else { + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") + } + } + buf.Flush() + return +} + +func (r *RuneBuffer) Clean() { + r.Lock() + r.clean() + r.Unlock() +} + +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) +} + +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { + if r.hadClean || !r.interactive { + return + } + r.hadClean = true + r.cleanOutput(r.w, idxLine) +} diff --git a/vendor/github.com/chzyer/readline/runes.go b/vendor/github.com/chzyer/readline/runes.go new file mode 100644 index 000000000..a669bc48c --- /dev/null +++ b/vendor/github.com/chzyer/readline/runes.go @@ -0,0 +1,223 @@ +package readline + +import ( + "bytes" + "unicode" + "unicode/utf8" +) + +var runes = Runes{} +var TabWidth = 4 + +type Runes struct{} + +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + +func (Runes) Equal(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { + for i := len(r) - len(sub); i >= 0; i-- { + found := true + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + +// Search in runes from front to end +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { + for i := 0; i < len(r); i++ { + found := true + if len(r[i:]) < len(sub) { + return -1 + } + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +func (Runes) Index(r rune, rs []rune) int { + for i := 0; i < len(rs); i++ { + if rs[i] == r { + return i + } + } + return -1 +} + +func (Runes) ColorFilter(r []rune) []rune { + newr := make([]rune, 0, len(r)) + for pos := 0; pos < len(r); pos++ { + if r[pos] == '\033' && r[pos+1] == '[' { + idx := runes.Index('m', r[pos+2:]) + if idx == -1 { + continue + } + pos += idx + 2 + continue + } + newr = append(newr, r[pos]) + } + return newr +} + +var zeroWidth = []*unicode.RangeTable{ + unicode.Mn, + unicode.Me, + unicode.Cc, + unicode.Cf, +} + +var doubleWidth = []*unicode.RangeTable{ + unicode.Han, + unicode.Hangul, + unicode.Hiragana, + unicode.Katakana, +} + +func (Runes) Width(r rune) int { + if r == '\t' { + return TabWidth + } + if unicode.IsOneOf(zeroWidth, r) { + return 0 + } + if unicode.IsOneOf(doubleWidth, r) { + return 2 + } + return 1 +} + +func (Runes) WidthAll(r []rune) (length int) { + for i := 0; i < len(r); i++ { + length += runes.Width(r[i]) + } + return +} + +func (Runes) Backspace(r []rune) []byte { + return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r)) +} + +func (Runes) Copy(r []rune) []rune { + n := make([]rune, len(r)) + copy(n, r) + return n +} + +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + +func (Runes) HasPrefix(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.Equal(r[:len(prefix)], prefix) +} + +func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) { + for i := 0; i < len(candicate[0]); i++ { + for j := 0; j < len(candicate)-1; j++ { + if i >= len(candicate[j]) || i >= len(candicate[j+1]) { + goto aggregate + } + if candicate[j][i] != candicate[j+1][i] { + goto aggregate + } + } + size = i + 1 + } +aggregate: + if size > 0 { + same = runes.Copy(candicate[0][:size]) + for i := 0; i < len(candicate); i++ { + n := runes.Copy(candicate[i]) + copy(n, n[size:]) + candicate[i] = n[:len(n)-size] + } + } + return +} + +func (Runes) TrimSpaceLeft(in []rune) []rune { + firstIndex := len(in) + for i, r := range in { + if unicode.IsSpace(r) == false { + firstIndex = i + break + } + } + return in[firstIndex:] +} diff --git a/vendor/github.com/chzyer/readline/search.go b/vendor/github.com/chzyer/readline/search.go new file mode 100644 index 000000000..52e8ff099 --- /dev/null +++ b/vendor/github.com/chzyer/readline/search.go @@ -0,0 +1,164 @@ +package readline + +import ( + "bytes" + "container/list" + "fmt" + "io" +) + +const ( + S_STATE_FOUND = iota + S_STATE_FAILING +) + +const ( + S_DIR_BCK = iota + S_DIR_FWD +) + +type opSearch struct { + inMode bool + state int + dir int + source *list.Element + w io.Writer + buf *RuneBuffer + data []rune + history *opHistory + cfg *Config + markStart int + markEnd int + width int +} + +func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { + return &opSearch{ + w: w, + buf: buf, + cfg: cfg, + history: history, + width: width, + } +} + +func (o *opSearch) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opSearch) IsSearchMode() bool { + return o.inMode +} + +func (o *opSearch) SearchBackspace() { + if len(o.data) > 0 { + o.data = o.data[:len(o.data)-1] + o.search(true) + } +} + +func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { + if o.dir == S_DIR_BCK { + return o.history.FindBck(isNewSearch, o.data, o.buf.idx) + } + return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) +} + +func (o *opSearch) search(isChange bool) bool { + if len(o.data) == 0 { + o.state = S_STATE_FOUND + o.SearchRefresh(-1) + return true + } + idx, elem := o.findHistoryBy(isChange) + if elem == nil { + o.SearchRefresh(-2) + return false + } + o.history.current = elem + + item := o.history.showItem(o.history.current.Value) + start, end := 0, 0 + if o.dir == S_DIR_BCK { + start, end = idx, idx+len(o.data) + } else { + start, end = idx, idx+len(o.data) + idx += len(o.data) + } + o.buf.SetWithIdx(idx, item) + o.markStart, o.markEnd = start, end + o.SearchRefresh(idx) + return true +} + +func (o *opSearch) SearchChar(r rune) { + o.data = append(o.data, r) + o.search(true) +} + +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } + alreadyInMode := o.inMode + o.inMode = true + o.dir = dir + o.source = o.history.current + if alreadyInMode { + o.search(false) + } else { + o.SearchRefresh(-1) + } + return true +} + +func (o *opSearch) ExitSearchMode(revert bool) { + if revert { + o.history.current = o.source + o.buf.Set(o.history.showItem(o.history.current.Value)) + } + o.markStart, o.markEnd = 0, 0 + o.state = S_STATE_FOUND + o.inMode = false + o.source = nil + o.data = nil +} + +func (o *opSearch) SearchRefresh(x int) { + if x == -2 { + o.state = S_STATE_FAILING + } else if x >= 0 { + o.state = S_STATE_FOUND + } + if x < 0 { + x = o.buf.idx + } + x = o.buf.CurrentWidth(x) + x += o.buf.PromptLen() + x = x % o.width + + if o.markStart > 0 { + o.buf.SetStyle(o.markStart, o.markEnd, "4") + } + + lineCnt := o.buf.CursorLineCount() + buf := bytes.NewBuffer(nil) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + buf.WriteString("\033[J") + if o.state == S_STATE_FAILING { + buf.WriteString("failing ") + } + if o.dir == S_DIR_BCK { + buf.WriteString("bck") + } else if o.dir == S_DIR_FWD { + buf.WriteString("fwd") + } + buf.WriteString("-i-search: ") + buf.WriteString(string(o.data)) // keyword + buf.WriteString("\033[4m \033[0m") // _ + fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev + if x > 0 { + fmt.Fprintf(buf, "\033[%dC", x) // move forward + } + o.w.Write(buf.Bytes()) +} diff --git a/vendor/github.com/chzyer/readline/std.go b/vendor/github.com/chzyer/readline/std.go new file mode 100644 index 000000000..61d44b759 --- /dev/null +++ b/vendor/github.com/chzyer/readline/std.go @@ -0,0 +1,197 @@ +package readline + +import ( + "io" + "os" + "sync" + "sync/atomic" +) + +var ( + Stdin io.ReadCloser = os.Stdin + Stdout io.WriteCloser = os.Stdout + Stderr io.WriteCloser = os.Stderr +) + +var ( + std *Instance + stdOnce sync.Once +) + +// global instance will not submit history automatic +func getInstance() *Instance { + stdOnce.Do(func() { + std, _ = NewEx(&Config{ + DisableAutoSaveHistory: true, + }) + }) + return std +} + +// let readline load history from filepath +// and try to persist history into disk +// set fp to "" to prevent readline persisting history to disk +// so the `AddHistory` will return nil error forever. +func SetHistoryPath(fp string) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.HistoryFile = fp + ins.SetConfig(cfg) +} + +// set auto completer to global instance +func SetAutoComplete(completer AutoCompleter) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.AutoComplete = completer + ins.SetConfig(cfg) +} + +// add history to global instance manually +// raise error only if `SetHistoryPath` is set with a non-empty path +func AddHistory(content string) error { + ins := getInstance() + return ins.SaveHistory(content) +} + +func Password(prompt string) ([]byte, error) { + ins := getInstance() + return ins.ReadPassword(prompt) +} + +// readline with global configs +func Line(prompt string) (string, error) { + ins := getInstance() + ins.SetPrompt(prompt) + return ins.Readline() +} + +type CancelableStdin struct { + r io.Reader + mutex sync.Mutex + stop chan struct{} + closed int32 + notify chan struct{} + data []byte + read int + err error +} + +func NewCancelableStdin(r io.Reader) *CancelableStdin { + c := &CancelableStdin{ + r: r, + notify: make(chan struct{}), + stop: make(chan struct{}), + } + go c.ioloop() + return c +} + +func (c *CancelableStdin) ioloop() { +loop: + for { + select { + case <-c.notify: + c.read, c.err = c.r.Read(c.data) + select { + case c.notify <- struct{}{}: + case <-c.stop: + break loop + } + case <-c.stop: + break loop + } + } +} + +func (c *CancelableStdin) Read(b []byte) (n int, err error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if atomic.LoadInt32(&c.closed) == 1 { + return 0, io.EOF + } + + c.data = b + select { + case c.notify <- struct{}{}: + case <-c.stop: + return 0, io.EOF + } + select { + case <-c.notify: + return c.read, c.err + case <-c.stop: + return 0, io.EOF + } +} + +func (c *CancelableStdin) Close() error { + if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { + close(c.stop) + } + return nil +} + +// FillableStdin is a stdin reader which can prepend some data before +// reading into the real stdin +type FillableStdin struct { + sync.Mutex + stdin io.Reader + stdinBuffer io.ReadCloser + buf []byte + bufErr error +} + +// NewFillableStdin gives you FillableStdin +func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) { + r, w := io.Pipe() + s := &FillableStdin{ + stdinBuffer: r, + stdin: stdin, + } + s.ioloop() + return s, w +} + +func (s *FillableStdin) ioloop() { + go func() { + for { + bufR := make([]byte, 100) + var n int + n, s.bufErr = s.stdinBuffer.Read(bufR) + if s.bufErr != nil { + if s.bufErr == io.ErrClosedPipe { + break + } + } + s.Lock() + s.buf = append(s.buf, bufR[:n]...) + s.Unlock() + } + }() +} + +// Read will read from the local buffer and if no data, read from stdin +func (s *FillableStdin) Read(p []byte) (n int, err error) { + s.Lock() + i := len(s.buf) + if len(p) < i { + i = len(p) + } + if i > 0 { + n := copy(p, s.buf) + s.buf = s.buf[:0] + cerr := s.bufErr + s.bufErr = nil + s.Unlock() + return n, cerr + } + s.Unlock() + n, err = s.stdin.Read(p) + return n, err +} + +func (s *FillableStdin) Close() error { + s.stdinBuffer.Close() + return nil +} diff --git a/vendor/github.com/chzyer/readline/std_windows.go b/vendor/github.com/chzyer/readline/std_windows.go new file mode 100644 index 000000000..b10f91bcb --- /dev/null +++ b/vendor/github.com/chzyer/readline/std_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package readline + +func init() { + Stdin = NewRawReader() + Stdout = NewANSIWriter(Stdout) + Stderr = NewANSIWriter(Stderr) +} diff --git a/vendor/github.com/chzyer/readline/term.go b/vendor/github.com/chzyer/readline/term.go new file mode 100644 index 000000000..133993ca8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term.go @@ -0,0 +1,123 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + termios Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := getTermios(fd) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + + if termios, err := getTermios(fd); err != nil { + return nil, err + } else { + oldState.termios = *termios + } + + newState := oldState.termios + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + // newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + return &oldState, setTermios(fd, &newState) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := getTermios(fd) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + return setTermios(fd, &state.termios) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + oldState, err := getTermios(fd) + if err != nil { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if err := setTermios(fd, newState); err != nil { + return nil, err + } + + defer func() { + setTermios(fd, oldState) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/term_bsd.go b/vendor/github.com/chzyer/readline/term_bsd.go new file mode 100644 index 000000000..68b56ea6b --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_bsd.go @@ -0,0 +1,29 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_linux.go b/vendor/github.com/chzyer/readline/term_linux.go new file mode 100644 index 000000000..e3392b4ac --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_linux.go @@ -0,0 +1,33 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package readline + +import ( + "syscall" + "unsafe" +) + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_solaris.go b/vendor/github.com/chzyer/readline/term_solaris.go new file mode 100644 index 000000000..4c27273c7 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_solaris.go @@ -0,0 +1,32 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package readline + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} + +type Termios unix.Termios + +func getTermios(fd int) (*Termios, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + return (*Termios)(termios), nil +} + +func setTermios(fd int, termios *Termios) error { + return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios)) +} diff --git a/vendor/github.com/chzyer/readline/term_unix.go b/vendor/github.com/chzyer/readline/term_unix.go new file mode 100644 index 000000000..d3ea24244 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_unix.go @@ -0,0 +1,24 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +type Termios syscall.Termios + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + var dimensions [4]uint16 + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0) + if err != 0 { + return 0, 0, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} diff --git a/vendor/github.com/chzyer/readline/term_windows.go b/vendor/github.com/chzyer/readline/term_windows.go new file mode 100644 index 000000000..1290e00bc --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_windows.go @@ -0,0 +1,171 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/terminal.go b/vendor/github.com/chzyer/readline/terminal.go new file mode 100644 index 000000000..1078631c1 --- /dev/null +++ b/vendor/github.com/chzyer/readline/terminal.go @@ -0,0 +1,238 @@ +package readline + +import ( + "bufio" + "fmt" + "io" + "strings" + "sync" + "sync/atomic" +) + +type Terminal struct { + m sync.Mutex + cfg *Config + outchan chan rune + closed int32 + stopChan chan struct{} + kickChan chan struct{} + wg sync.WaitGroup + isReading int32 + sleeping int32 + + sizeChan chan string +} + +func NewTerminal(cfg *Config) (*Terminal, error) { + if err := cfg.Init(); err != nil { + return nil, err + } + t := &Terminal{ + cfg: cfg, + kickChan: make(chan struct{}, 1), + outchan: make(chan rune), + stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), + } + + go t.ioloop() + return t, nil +} + +// SleepToResume will sleep myself, and return only if I'm resumed. +func (t *Terminal) SleepToResume() { + if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { + return + } + defer atomic.StoreInt32(&t.sleeping, 0) + + t.ExitRawMode() + ch := WaitForResume() + SuspendMe() + <-ch + t.EnterRawMode() +} + +func (t *Terminal) EnterRawMode() (err error) { + return t.cfg.FuncMakeRaw() +} + +func (t *Terminal) ExitRawMode() (err error) { + return t.cfg.FuncExitRaw() +} + +func (t *Terminal) Write(b []byte) (int, error) { + return t.cfg.Stdout.Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +func (t *Terminal) WriteStdin(b []byte) (int, error) { + return t.cfg.StdinWriter.Write(b) +} + +type termSize struct { + left int + top int +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + +func (t *Terminal) Print(s string) { + fmt.Fprintf(t.cfg.Stdout, "%s", s) +} + +func (t *Terminal) PrintRune(r rune) { + fmt.Fprintf(t.cfg.Stdout, "%c", r) +} + +func (t *Terminal) Readline() *Operation { + return NewOperation(t, t.cfg) +} + +// return rune(0) if meet EOF +func (t *Terminal) ReadRune() rune { + ch, ok := <-t.outchan + if !ok { + return rune(0) + } + return ch +} + +func (t *Terminal) IsReading() bool { + return atomic.LoadInt32(&t.isReading) == 1 +} + +func (t *Terminal) KickRead() { + select { + case t.kickChan <- struct{}{}: + default: + } +} + +func (t *Terminal) ioloop() { + t.wg.Add(1) + defer func() { + t.wg.Done() + close(t.outchan) + }() + + var ( + isEscape bool + isEscapeEx bool + expectNextChar bool + ) + + buf := bufio.NewReader(t.getStdin()) + for { + if !expectNextChar { + atomic.StoreInt32(&t.isReading, 0) + select { + case <-t.kickChan: + atomic.StoreInt32(&t.isReading, 1) + case <-t.stopChan: + return + } + } + expectNextChar = false + r, _, err := buf.ReadRune() + if err != nil { + if strings.Contains(err.Error(), "interrupted system call") { + expectNextChar = true + continue + } + break + } + + if isEscape { + isEscape = false + if r == CharEscapeEx { + expectNextChar = true + isEscapeEx = true + continue + } + r = escapeKey(r, buf) + } else if isEscapeEx { + isEscapeEx = false + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } + } + + expectNextChar = true + switch r { + case CharEsc: + if t.cfg.VimMode { + t.outchan <- r + break + } + isEscape = true + case CharInterrupt, CharEnter, CharCtrlJ, CharDelete: + expectNextChar = false + fallthrough + default: + t.outchan <- r + } + } + +} + +func (t *Terminal) Bell() { + fmt.Fprintf(t, "%c", CharBell) +} + +func (t *Terminal) Close() error { + if atomic.SwapInt32(&t.closed, 1) != 0 { + return nil + } + if closer, ok := t.cfg.Stdin.(io.Closer); ok { + closer.Close() + } + close(t.stopChan) + t.wg.Wait() + return t.ExitRawMode() +} + +func (t *Terminal) GetConfig() *Config { + t.m.Lock() + cfg := *t.cfg + t.m.Unlock() + return &cfg +} + +func (t *Terminal) getStdin() io.Reader { + t.m.Lock() + r := t.cfg.Stdin + t.m.Unlock() + return r +} + +func (t *Terminal) SetConfig(c *Config) error { + if err := c.Init(); err != nil { + return err + } + t.m.Lock() + t.cfg = c + t.m.Unlock() + return nil +} diff --git a/vendor/github.com/chzyer/readline/utils.go b/vendor/github.com/chzyer/readline/utils.go new file mode 100644 index 000000000..af4e00521 --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils.go @@ -0,0 +1,277 @@ +package readline + +import ( + "bufio" + "bytes" + "container/list" + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode" +) + +var ( + isWindows = false +) + +const ( + CharLineStart = 1 + CharBackward = 2 + CharInterrupt = 3 + CharDelete = 4 + CharLineEnd = 5 + CharForward = 6 + CharBell = 7 + CharCtrlH = 8 + CharTab = 9 + CharCtrlJ = 10 + CharKill = 11 + CharCtrlL = 12 + CharEnter = 13 + CharNext = 14 + CharPrev = 16 + CharBckSearch = 18 + CharFwdSearch = 19 + CharTranspose = 20 + CharCtrlU = 21 + CharCtrlW = 23 + CharCtrlY = 25 + CharCtrlZ = 26 + CharEsc = 27 + CharEscapeEx = 91 + CharBackspace = 127 +) + +const ( + MetaBackward rune = -iota - 1 + MetaForward + MetaDelete + MetaBackspace + MetaTranspose +) + +// WaitForResume need to call before current process got suspend. +// It will run a ticker until a long duration is occurs, +// which means this process is resumed. +func WaitForResume() chan struct{} { + ch := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + ticker := time.NewTicker(10 * time.Millisecond) + t := time.Now() + wg.Done() + for { + now := <-ticker.C + if now.Sub(t) > 100*time.Millisecond { + break + } + t = now + } + ticker.Stop() + ch <- struct{}{} + }() + wg.Wait() + return ch +} + +func Restore(fd int, state *State) error { + err := restoreTerm(fd, state) + if err != nil { + // errno 0 means everything is ok :) + if err.Error() == "errno 0" { + return nil + } else { + return err + } + } + return nil +} + +func IsPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// translate Esc[X +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { + case 'D': + r = CharBackward + case 'C': + r = CharForward + case 'A': + r = CharPrev + case 'B': + r = CharNext + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + case '~': + if key.attr == "3" { + r = CharDelete + } + default: + } + return r +} + +type escapeKeyPair struct { + attr string + typ rune +} + +func (e *escapeKeyPair) Get2() (int, int, bool) { + sp := strings.Split(e.attr, ";") + if len(sp) < 2 { + return -1, -1, false + } + s1, err := strconv.Atoi(sp[0]) + if err != nil { + return -1, -1, false + } + s2, err := strconv.Atoi(sp[1]) + if err != nil { + return -1, -1, false + } + return s1, s2, true +} + +func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { + p := escapeKeyPair{} + buf := bytes.NewBuffer(nil) + for { + if r == ';' { + } else if unicode.IsNumber(r) { + } else { + p.typ = r + break + } + buf.WriteRune(r) + r, _, _ = reader.ReadRune() + } + p.attr = buf.String() + return &p +} + +// translate EscX to Meta+X +func escapeKey(r rune, reader *bufio.Reader) rune { + switch r { + case 'b': + r = MetaBackward + case 'f': + r = MetaForward + case 'd': + r = MetaDelete + case CharTranspose: + r = MetaTranspose + case CharBackspace: + r = MetaBackspace + case 'O': + d, _, _ := reader.ReadRune() + switch d { + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + default: + reader.UnreadRune() + } + case CharEsc: + + } + return r +} + +func SplitByLine(start, screenWidth int, rs []rune) []string { + var ret []string + buf := bytes.NewBuffer(nil) + currentWidth := start + for _, r := range rs { + w := runes.Width(r) + currentWidth += w + buf.WriteRune(r) + if currentWidth >= screenWidth { + ret = append(ret, buf.String()) + buf.Reset() + currentWidth = 0 + } + } + ret = append(ret, buf.String()) + return ret +} + +// calculate how many lines for N character +func LineCount(screenWidth, w int) int { + r := w / screenWidth + if w%screenWidth != 0 { + r++ + } + return r +} + +func IsWordBreak(i rune) bool { + switch { + case i >= 'a' && i <= 'z': + case i >= 'A' && i <= 'Z': + case i >= '0' && i <= '9': + default: + return true + } + return false +} + +func GetInt(s []string, def int) int { + if len(s) == 0 { + return def + } + c, err := strconv.Atoi(s[0]) + if err != nil { + return def + } + return c +} + +type RawMode struct { + state *State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = MakeRaw(GetStdin()) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + return Restore(GetStdin(), r.state) +} + +// ----------------------------------------------------------------------------- + +func sleep(n int) { + Debug(n) + time.Sleep(2000 * time.Millisecond) +} + +// print a linked list to Debug() +func debugList(l *list.List) { + idx := 0 + for e := l.Front(); e != nil; e = e.Next() { + Debug(idx, fmt.Sprintf("%+v", e.Value)) + idx++ + } +} + +// append log info to another file +func Debug(o ...interface{}) { + f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fmt.Fprintln(f, o...) + f.Close() +} diff --git a/vendor/github.com/chzyer/readline/utils_unix.go b/vendor/github.com/chzyer/readline/utils_unix.go new file mode 100644 index 000000000..f88dac97b --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_unix.go @@ -0,0 +1,83 @@ +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +package readline + +import ( + "io" + "os" + "os/signal" + "sync" + "syscall" +) + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// SuspendMe use to send suspend signal to myself, when we in the raw mode. +// For OSX it need to send to parent's pid +// For Linux it need to send to myself +func SuspendMe() { + p, _ := os.FindProcess(os.Getppid()) + p.Signal(syscall.SIGTSTP) + p, _ = os.FindProcess(os.Getpid()) + p.Signal(syscall.SIGTSTP) +} + +// get width of the terminal +func getWidth(stdoutFd int) int { + cols, _, err := GetSize(stdoutFd) + if err != nil { + return -1 + } + return cols +} + +func GetScreenWidth() int { + w := getWidth(syscall.Stdout) + if w < 0 { + w = getWidth(syscall.Stderr) + } + return w +} + +// ClearScreen clears the console screen +func ClearScreen(w io.Writer) (int, error) { + return w.Write([]byte("\033[H")) +} + +func DefaultIsTerminal() bool { + return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr)) +} + +func GetStdin() int { + return syscall.Stdin +} + +// ----------------------------------------------------------------------------- + +var ( + widthChange sync.Once + widthChangeCallback func() +) + +func DefaultOnWidthChanged(f func()) { + widthChangeCallback = f + widthChange.Do(func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + + go func() { + for { + _, ok := <-ch + if !ok { + break + } + widthChangeCallback() + } + }() + }) +} diff --git a/vendor/github.com/chzyer/readline/utils_windows.go b/vendor/github.com/chzyer/readline/utils_windows.go new file mode 100644 index 000000000..5bfa55dcc --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package readline + +import ( + "io" + "syscall" +) + +func SuspendMe() { +} + +func GetStdin() int { + return int(syscall.Stdin) +} + +func init() { + isWindows = true +} + +// get width of the terminal +func GetScreenWidth() int { + info, _ := GetConsoleScreenBufferInfo() + if info == nil { + return -1 + } + return int(info.dwSize.x) +} + +// ClearScreen clears the console screen +func ClearScreen(_ io.Writer) error { + return SetConsoleCursorPosition(&_COORD{0, 0}) +} + +func DefaultIsTerminal() bool { + return true +} + +func DefaultOnWidthChanged(func()) { + +} diff --git a/vendor/github.com/chzyer/readline/vim.go b/vendor/github.com/chzyer/readline/vim.go new file mode 100644 index 000000000..bedf2c1a6 --- /dev/null +++ b/vendor/github.com/chzyer/readline/vim.go @@ -0,0 +1,176 @@ +package readline + +const ( + VIM_NORMAL = iota + VIM_INSERT + VIM_VISUAL +) + +type opVim struct { + cfg *Config + op *Operation + vimMode int +} + +func newVimMode(op *Operation) *opVim { + ov := &opVim{ + cfg: op.cfg, + op: op, + } + ov.SetVimMode(ov.cfg.VimMode) + return ov +} + +func (o *opVim) SetVimMode(on bool) { + if o.cfg.VimMode && !on { // turn off + o.ExitVimMode() + } + o.cfg.VimMode = on + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) IsEnableVimMode() bool { + return o.cfg.VimMode +} + +func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'h': + t = CharBackward + case 'j': + t = CharNext + case 'k': + t = CharPrev + case 'l': + t = CharForward + case '0', '^': + rb.MoveToLineStart() + case '$': + rb.MoveToLineEnd() + case 'x': + rb.Delete() + if rb.IsCursorInEnd() { + rb.MoveBackward() + } + case 'r': + rb.Replace(readNext()) + case 'd': + next := readNext() + switch next { + case 'd': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + case 'p': + rb.Yank() + case 'b', 'B': + rb.MoveToPrevWord() + case 'w', 'W': + rb.MoveToNextWord() + case 'e', 'E': + rb.MoveToEndWord() + case 'f', 'F', 't', 'T': + next := readNext() + prevChar := r == 't' || r == 'T' + reverse := r == 'F' || r == 'T' + switch next { + case CharEsc: + default: + rb.MoveTo(next, prevChar, reverse) + } + default: + return r, false + } + return t, true +} + +func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'i': + case 'I': + rb.MoveToLineStart() + case 'a': + rb.MoveForward() + case 'A': + rb.MoveToLineEnd() + case 's': + rb.Delete() + case 'S': + rb.Erase() + case 'c': + next := readNext() + switch next { + case 'c': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + default: + return r, false + } + + o.EnterVimInsertMode() + return +} + +func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) { + switch r { + case CharEnter, CharInterrupt: + o.ExitVimMode() + return r + } + + if r, handled := o.handleVimNormalMovement(r, readNext); handled { + return r + } + + if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled { + return r + } + + // invalid operation + o.op.t.Bell() + return 0 +} + +func (o *opVim) EnterVimInsertMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimInsertMode() { + o.vimMode = VIM_NORMAL +} + +func (o *opVim) HandleVim(r rune, readNext func() rune) rune { + if o.vimMode == VIM_NORMAL { + return o.HandleVimNormal(r, readNext) + } + if r == CharEsc { + o.ExitVimInsertMode() + return 0 + } + + switch o.vimMode { + case VIM_INSERT: + return r + case VIM_VISUAL: + } + return r +} diff --git a/vendor/github.com/chzyer/readline/windows_api.go b/vendor/github.com/chzyer/readline/windows_api.go new file mode 100644 index 000000000..63f4f7b78 --- /dev/null +++ b/vendor/github.com/chzyer/readline/windows_api.go @@ -0,0 +1,152 @@ +// +build windows + +package readline + +import ( + "reflect" + "syscall" + "unsafe" +) + +var ( + kernel = NewKernel() + stdout = uintptr(syscall.Stdout) + stdin = uintptr(syscall.Stdin) +) + +type Kernel struct { + SetConsoleCursorPosition, + SetConsoleTextAttribute, + FillConsoleOutputCharacterW, + FillConsoleOutputAttribute, + ReadConsoleInputW, + GetConsoleScreenBufferInfo, + GetConsoleCursorInfo, + GetStdHandle CallFunc +} + +type short int16 +type word uint16 +type dword uint32 +type wchar uint16 + +type _COORD struct { + x short + y short +} + +func (c *_COORD) ptr() uintptr { + return uintptr(*(*int32)(unsafe.Pointer(c))) +} + +const ( + EVENT_KEY = 0x0001 + EVENT_MOUSE = 0x0002 + EVENT_WINDOW_BUFFER_SIZE = 0x0004 + EVENT_MENU = 0x0008 + EVENT_FOCUS = 0x0010 +) + +type _KEY_EVENT_RECORD struct { + bKeyDown int32 + wRepeatCount word + wVirtualKeyCode word + wVirtualScanCode word + unicodeChar wchar + dwControlKeyState dword +} + +// KEY_EVENT_RECORD KeyEvent; +// MOUSE_EVENT_RECORD MouseEvent; +// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; +// MENU_EVENT_RECORD MenuEvent; +// FOCUS_EVENT_RECORD FocusEvent; +type _INPUT_RECORD struct { + EventType word + Padding uint16 + Event [16]byte +} + +type _CONSOLE_SCREEN_BUFFER_INFO struct { + dwSize _COORD + dwCursorPosition _COORD + wAttributes word + srWindow _SMALL_RECT + dwMaximumWindowSize _COORD +} + +type _SMALL_RECT struct { + left short + top short + right short + bottom short +} + +type _CONSOLE_CURSOR_INFO struct { + dwSize dword + bVisible bool +} + +type CallFunc func(u ...uintptr) error + +func NewKernel() *Kernel { + k := &Kernel{} + kernel32 := syscall.NewLazyDLL("kernel32.dll") + v := reflect.ValueOf(k).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + name := t.Field(i).Name + f := kernel32.NewProc(name) + v.Field(i).Set(reflect.ValueOf(k.Wrap(f))) + } + return k +} + +func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc { + return func(args ...uintptr) error { + var r0 uintptr + var e1 syscall.Errno + size := uintptr(len(args)) + if len(args) <= 3 { + buf := make([]uintptr, 3) + copy(buf, args) + r0, _, e1 = syscall.Syscall(p.Addr(), size, + buf[0], buf[1], buf[2]) + } else { + buf := make([]uintptr, 6) + copy(buf, args) + r0, _, e1 = syscall.Syscall6(p.Addr(), size, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + ) + } + + if int(r0) == 0 { + if e1 != 0 { + return error(e1) + } else { + return syscall.EINVAL + } + } + return nil + } + +} + +func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { + t := new(_CONSOLE_SCREEN_BUFFER_INFO) + err := kernel.GetConsoleScreenBufferInfo( + stdout, + uintptr(unsafe.Pointer(t)), + ) + return t, err +} + +func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { + t := new(_CONSOLE_CURSOR_INFO) + err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) + return t, err +} + +func SetConsoleCursorPosition(c *_COORD) error { + return kernel.SetConsoleCursorPosition(stdout, c.ptr()) +} diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile index 7b2cfcf81..e70dd161d 100644 --- a/vendor/github.com/containers/buildah/Makefile +++ b/vendor/github.com/containers/buildah/Makefile @@ -34,7 +34,7 @@ RUNC_COMMIT := v1.0.0-rc8 LIBSECCOMP_COMMIT := release-2.3 EXTRA_LDFLAGS ?= -LDFLAGS := -ldflags '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(EXTRA_LDFLAGS)' +BUILDAH_LDFLAGS := -ldflags '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(EXTRA_LDFLAGS)' SOURCES=*.go imagebuildah/*.go bind/*.go chroot/*.go cmd/buildah/*.go copier/*.go docker/*.go pkg/blobcache/*.go pkg/cli/*.go pkg/parse/*.go util/*.go LINTFLAGS ?= @@ -56,7 +56,7 @@ static: .PHONY: bin/buildah bin/buildah: $(SOURCES) - $(GO_BUILD) $(LDFLAGS) -o $@ $(BUILDFLAGS) ./cmd/buildah + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./cmd/buildah .PHONY: buildah buildah: bin/buildah @@ -67,11 +67,11 @@ cross: bin/buildah.darwin.amd64 bin/buildah.linux.386 bin/buildah.linux.amd64 bi .PHONY: bin/buildah.% bin/buildah.%: mkdir -p ./bin - GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah + GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah .PHONY: bin/imgtype bin/imgtype: *.go docker/*.go util/*.go tests/imgtype/imgtype.go - $(GO_BUILD) $(LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go .PHONY: clean clean: diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go index 80ee0d912..6cfd6a09f 100644 --- a/vendor/github.com/containers/buildah/add.go +++ b/vendor/github.com/containers/buildah/add.go @@ -71,7 +71,7 @@ func sourceIsRemote(source string) bool { } // getURL writes a tar archive containing the named content -func getURL(src, mountpoint, renameTarget string, writer io.Writer) error { +func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer) error { url, err := url.Parse(src) if err != nil { return err @@ -122,10 +122,18 @@ func getURL(src, mountpoint, renameTarget string, writer io.Writer) error { // Write the output archive. Set permissions for compatibility. tw := tar.NewWriter(writer) defer tw.Close() + uid := 0 + gid := 0 + if chown != nil { + uid = chown.UID + gid = chown.GID + } hdr := tar.Header{ Typeflag: tar.TypeReg, Name: name, Size: size, + Uid: uid, + Gid: gid, Mode: 0600, ModTime: date, } @@ -323,7 +331,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption pipeReader, pipeWriter := io.Pipe() wg.Add(1) go func() { - getErr = getURL(src, mountPoint, renameTarget, pipeWriter) + getErr = getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter) pipeWriter.Close() wg.Done() }() @@ -341,9 +349,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption putOptions := copier.PutOptions{ UIDMap: destUIDMap, GIDMap: destGIDMap, - ChownDirs: chownDirs, + ChownDirs: nil, ChmodDirs: nil, - ChownFiles: chownFiles, + ChownFiles: nil, ChmodFiles: nil, } putErr = copier.Put(mountPoint, extractDirectory, putOptions, io.TeeReader(pipeReader, hasher)) diff --git a/vendor/github.com/containers/buildah/btrfs_installed_tag.sh b/vendor/github.com/containers/buildah/btrfs_installed_tag.sh index c4d99f377..f2f2b33c8 100644 --- a/vendor/github.com/containers/buildah/btrfs_installed_tag.sh +++ b/vendor/github.com/containers/buildah/btrfs_installed_tag.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -cc -E - > /dev/null 2> /dev/null << EOF +${CPP:-${CC:-cc} -E} ${CPPFLAGS} - > /dev/null 2> /dev/null << EOF #include <btrfs/ioctl.h> EOF if test $? -ne 0 ; then diff --git a/vendor/github.com/containers/buildah/btrfs_tag.sh b/vendor/github.com/containers/buildah/btrfs_tag.sh index 59cb969ad..ea753d4d0 100644 --- a/vendor/github.com/containers/buildah/btrfs_tag.sh +++ b/vendor/github.com/containers/buildah/btrfs_tag.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -cc -E - > /dev/null 2> /dev/null << EOF +${CPP:-${CC:-cc} -E} ${CPPFLAGS} - > /dev/null 2> /dev/null << EOF #include <btrfs/version.h> EOF if test $? -ne 0 ; then diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 86695508c..96e8619a8 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -28,7 +28,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.17.0" + Version = "1.18.0-dev" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/copier/copier.go b/vendor/github.com/containers/buildah/copier/copier.go index 9ebc8e2a3..84b636202 100644 --- a/vendor/github.com/containers/buildah/copier/copier.go +++ b/vendor/github.com/containers/buildah/copier/copier.go @@ -7,7 +7,9 @@ import ( "fmt" "io" "io/ioutil" + "net" "os" + "os/user" "path/filepath" "strconv" "strings" @@ -35,6 +37,14 @@ const ( func init() { reexec.Register(copierCommand, copierMain) + // Attempt a user and host lookup to force libc (glibc, and possibly others that use dynamic + // modules to handle looking up user and host information) to load modules that match the libc + // our binary is currently using. Hopefully they're loaded on first use, so that they won't + // need to be loaded after we've chrooted into the rootfs, which could include modules that + // don't match our libc and which can't be loaded, or modules which we don't want to execute + // because we don't trust their code. + _, _ = user.Lookup("buildah") + _, _ = net.LookupHost("localhost") } // isArchivePath returns true if the specified path can be read like a (possibly diff --git a/vendor/github.com/containers/buildah/copier/xattrs.go b/vendor/github.com/containers/buildah/copier/xattrs.go index 71769989c..c757adcc8 100644 --- a/vendor/github.com/containers/buildah/copier/xattrs.go +++ b/vendor/github.com/containers/buildah/copier/xattrs.go @@ -45,6 +45,11 @@ func Lgetxattrs(path string) (map[string]string, error) { listSize *= 2 continue } + if (unwrapError(err) == syscall.ENOTSUP) || (unwrapError(err) == syscall.ENOSYS) { + // treat these errors listing xattrs as equivalent to "no xattrs" + list = list[:0] + break + } return nil, errors.Wrapf(err, "error listing extended attributes of %q", path) } list = list[:size] diff --git a/vendor/github.com/containers/buildah/go.mod b/vendor/github.com/containers/buildah/go.mod index 6fe683f4b..2bc71f948 100644 --- a/vendor/github.com/containers/buildah/go.mod +++ b/vendor/github.com/containers/buildah/go.mod @@ -5,10 +5,10 @@ go 1.12 require ( github.com/containerd/containerd v1.4.1 // indirect github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 - github.com/containers/common v0.26.2 - github.com/containers/image/v5 v5.7.0 + github.com/containers/common v0.26.3 + github.com/containers/image/v5 v5.8.0 github.com/containers/ocicrypt v1.0.3 - github.com/containers/storage v1.23.7 + github.com/containers/storage v1.23.9 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible // indirect github.com/docker/go-units v0.4.0 @@ -17,6 +17,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/hashicorp/go-multierror v1.1.0 github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mattn/go-shellwords v1.0.10 github.com/moby/sys/mount v0.1.1 // indirect github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 // indirect diff --git a/vendor/github.com/containers/buildah/go.sum b/vendor/github.com/containers/buildah/go.sum index 65268af4e..1952ace1a 100644 --- a/vendor/github.com/containers/buildah/go.sum +++ b/vendor/github.com/containers/buildah/go.sum @@ -45,6 +45,12 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -65,17 +71,20 @@ github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDG github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 h1:rqUVLD8I859xRgUx/WMC3v7QAFqbLKZbs+0kqYboRJc= github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containers/common v0.26.2 h1:TysMCBpzq3gDFD9GzM0TKTGjtq/9HySWevKtlrvVGRU= -github.com/containers/common v0.26.2/go.mod h1:igUeog5hx8rYhJk67rG6rGAh3zEcf0Uxuzm9KpXzo2E= +github.com/containers/common v0.26.3 h1:5Kb5fMmJ7/xMiJ+iEbPA+5pQpl/FGxCgJex4nml4Slo= +github.com/containers/common v0.26.3/go.mod h1:hJWZIlrl5MsE2ELNRa+MPp6I1kPbXHauuj0Ym4BsLG4= github.com/containers/image/v5 v5.7.0 h1:fiTC8/Xbr+zEP6njGTZtPW/3UD7MC93nC9DbUoWdxkA= github.com/containers/image/v5 v5.7.0/go.mod h1:8aOy+YaItukxghRORkvhq5ibWttHErzDLy6egrKfKos= +github.com/containers/image/v5 v5.8.0 h1:B3FGHi0bdGXgg698kBIGOlHCXN5n+scJr6/5354GOPU= +github.com/containers/image/v5 v5.8.0/go.mod h1:jKxdRtyIDumVa56hdsZvV+gwx4zB50hRou6pIuCWLkg= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c= github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBDb5f8P5CAxJY= -github.com/containers/storage v1.23.7 h1:43ImvG/npvQSZXRjaudVvKISIuZSfI6qvtSNQQSGO/A= github.com/containers/storage v1.23.7/go.mod h1:cUT2zHjtx+WlVri30obWmM2gpqpi8jfPsmIzP1TVpEI= +github.com/containers/storage v1.23.9 h1:qbgnTp76pLSyW3vYwY5GH4vk5cHYVXFJ+CsUEBp9TMw= +github.com/containers/storage v1.23.9/go.mod h1:3b2ktpB6pw53SEeIoFfO0sQfP9+IoJJKPq5iJk74gxE= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -214,25 +223,37 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE= github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= +github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= +github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= @@ -479,6 +500,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 76dfeaf54..a97a403b3 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -216,20 +216,19 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt } data = resp.Body } else { - // If the Dockerfile isn't found try prepending the - // context directory to it. dinfo, err := os.Stat(dfile) - if os.IsNotExist(err) { - // If they are "/workDir/Dockerfile" and "/workDir" - // so don't joint it + if err != nil { + // If the Dockerfile isn't available, try again with + // context directory prepended (if not prepended yet). if !strings.HasPrefix(dfile, options.ContextDirectory) { dfile = filepath.Join(options.ContextDirectory, dfile) + dinfo, err = os.Stat(dfile) } - dinfo, err = os.Stat(dfile) - if err != nil { - return "", nil, err - } } + if err != nil { + return "", nil, err + } + // If given a directory, add '/Dockerfile' to it. if dinfo.Mode().IsDir() { dfile = filepath.Join(dfile, "Dockerfile") diff --git a/vendor/github.com/containers/buildah/imagebuildah/executor.go b/vendor/github.com/containers/buildah/imagebuildah/executor.go index 77c224ad8..8c96b4e67 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/executor.go @@ -17,6 +17,7 @@ import ( "github.com/containers/buildah/util" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -111,6 +112,15 @@ type Executor struct { stagesSemaphore *semaphore.Weighted jobs int logRusage bool + imageInfoLock sync.Mutex + imageInfoCache map[string]imageTypeAndHistoryAndDiffIDs +} + +type imageTypeAndHistoryAndDiffIDs struct { + manifestType string + history []v1.History + diffIDs []digest.Digest + err error } // NewExecutor creates a new instance of the imagebuilder.Executor interface. @@ -215,6 +225,7 @@ func NewExecutor(store storage.Store, options BuildOptions, mainNode *parser.Nod terminatedStage: make(map[string]struct{}), jobs: jobs, logRusage: options.LogRusage, + imageInfoCache: make(map[string]imageTypeAndHistoryAndDiffIDs), } if exec.err == nil { exec.err = os.Stderr @@ -335,22 +346,43 @@ func (b *Executor) waitForStage(ctx context.Context, name string, stages imagebu } } -// getImageHistoryAndDiffIDs returns the history and diff IDs list of imageID. -func (b *Executor) getImageHistoryAndDiffIDs(ctx context.Context, imageID string) ([]v1.History, []digest.Digest, error) { +// getImageTypeAndHistoryAndDiffIDs returns the manifest type, history, and diff IDs list of imageID. +func (b *Executor) getImageTypeAndHistoryAndDiffIDs(ctx context.Context, imageID string) (string, []v1.History, []digest.Digest, error) { + b.imageInfoLock.Lock() + imageInfo, ok := b.imageInfoCache[imageID] + b.imageInfoLock.Unlock() + if ok { + return imageInfo.manifestType, imageInfo.history, imageInfo.diffIDs, imageInfo.err + } imageRef, err := is.Transport.ParseStoreReference(b.store, "@"+imageID) if err != nil { - return nil, nil, errors.Wrapf(err, "error getting image reference %q", imageID) + return "", nil, nil, errors.Wrapf(err, "error getting image reference %q", imageID) } ref, err := imageRef.NewImage(ctx, nil) if err != nil { - return nil, nil, errors.Wrapf(err, "error creating new image from reference to image %q", imageID) + return "", nil, nil, errors.Wrapf(err, "error creating new image from reference to image %q", imageID) } defer ref.Close() oci, err := ref.OCIConfig(ctx) if err != nil { - return nil, nil, errors.Wrapf(err, "error getting possibly-converted OCI config of image %q", imageID) + return "", nil, nil, errors.Wrapf(err, "error getting possibly-converted OCI config of image %q", imageID) + } + manifestBytes, manifestFormat, err := ref.Manifest(ctx) + if err != nil { + return "", nil, nil, errors.Wrapf(err, "error getting manifest of image %q", imageID) + } + if manifestFormat == "" && len(manifestBytes) > 0 { + manifestFormat = manifest.GuessMIMEType(manifestBytes) + } + b.imageInfoLock.Lock() + b.imageInfoCache[imageID] = imageTypeAndHistoryAndDiffIDs{ + manifestType: manifestFormat, + history: oci.History, + diffIDs: oci.RootFS.DiffIDs, + err: nil, } - return oci.History, oci.RootFS.DiffIDs, nil + b.imageInfoLock.Unlock() + return manifestFormat, oci.History, oci.RootFS.DiffIDs, nil } func (b *Executor) buildStage(ctx context.Context, cleanupStages map[int]*StageExecutor, stages imagebuilder.Stages, stageIndex int) (imageID string, ref reference.Canonical, err error) { diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go index e157bb1c8..6c058e226 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go @@ -1110,7 +1110,7 @@ func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *p var baseHistory []v1.History var baseDiffIDs []digest.Digest if s.builder.FromImageID != "" { - baseHistory, baseDiffIDs, err = s.executor.getImageHistoryAndDiffIDs(ctx, s.builder.FromImageID) + _, baseHistory, baseDiffIDs, err = s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, s.builder.FromImageID) if err != nil { return "", errors.Wrapf(err, "error getting history of base image %q", s.builder.FromImageID) } @@ -1142,10 +1142,15 @@ func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *p } // Next we double check that the history of this image is equivalent to the previous // lines in the Dockerfile up till the point we are at in the build. - history, diffIDs, err := s.executor.getImageHistoryAndDiffIDs(ctx, image.ID) + manifestType, history, diffIDs, err := s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, image.ID) if err != nil { return "", errors.Wrapf(err, "error getting history of %q", image.ID) } + // If this candidate isn't of the type that we're building, then it may have lost + // some format-specific information that a building-without-cache run wouldn't lose. + if manifestType != s.executor.outputFormat { + continue + } // children + currNode is the point of the Dockerfile we are currently at. if s.historyAndDiffIDsMatch(baseHistory, baseDiffIDs, currNode, history, diffIDs, addedContentDigest, buildAddsLayer) { return image.ID, nil @@ -1276,5 +1281,5 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer } func (s *StageExecutor) EnsureContainerPath(path string) error { - return copier.Mkdir(s.mountPoint, path, copier.MkdirOptions{}) + return copier.Mkdir(s.mountPoint, filepath.Join(s.mountPoint, path), copier.MkdirOptions{}) } diff --git a/vendor/github.com/containers/buildah/libdm_tag.sh b/vendor/github.com/containers/buildah/libdm_tag.sh index d3668aab1..815b5d914 100644 --- a/vendor/github.com/containers/buildah/libdm_tag.sh +++ b/vendor/github.com/containers/buildah/libdm_tag.sh @@ -2,7 +2,7 @@ tmpdir="$PWD/tmp.$RANDOM" mkdir -p "$tmpdir" trap 'rm -fr "$tmpdir"' EXIT -cc -o "$tmpdir"/libdm_tag -ldevmapper -x c - > /dev/null 2> /dev/null << EOF +${CC:-cc} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} -o "$tmpdir"/libdm_tag -x c - -ldevmapper > /dev/null 2> /dev/null << EOF #include <libdevmapper.h> int main() { struct dm_task *task; diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index 4f4b1564b..c1abb1cdb 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "math/rand" + "os" "strings" - "time" "github.com/containers/buildah/util" + "github.com/containers/image/v5/docker" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/pkg/shortnames" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -103,145 +104,168 @@ func newContainerIDMappingOptions(idmapOptions *IDMappingOptions) storage.IDMapp return options } -func resolveImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { - type failure struct { - resolvedImageName string - err error - } - candidates, transport, searchRegistriesWereUsedButEmpty, err := util.ResolveName(options.FromImage, options.Registry, systemContext, store) +func resolveLocalImage(systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { + candidates, _, _, err := util.ResolveName(options.FromImage, options.Registry, systemContext, store) if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", options.FromImage) + return nil, "", nil, errors.Wrapf(err, "error resolving local image %q", options.FromImage) } - - failures := []failure{} for _, image := range candidates { - if transport == "" { - img, err := store.Image(image) - if err != nil { - logrus.Debugf("error looking up known-local image %q: %v", image, err) - failures = append(failures, failure{resolvedImageName: image, err: err}) + img, err := store.Image(image) + if err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { continue } - ref, err := is.Transport.ParseStoreReference(store, img.ID) - if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) - } - return ref, transport, img, nil + return nil, "", nil, err } - - trans := transport - if transport != util.DefaultTransport { - trans = trans + ":" - } - srcRef, err := alltransports.ParseImageName(trans + image) + ref, err := is.Transport.ParseStoreReference(store, img.ID) if err != nil { - logrus.Debugf("error parsing image name %q: %v", trans+image, err) - failures = append(failures, failure{ - resolvedImageName: image, - err: errors.Wrapf(err, "error parsing attempted image name %q", trans+image), - }) - continue + return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) } + return ref, ref.Transport().Name(), img, nil + } + + return nil, "", nil, nil +} + +// getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment +// variable. If it's "on", return `nil` to use the defaults from +// containers/image and the registries.conf files on the system. If it's +// "off", empty or unset, return types.ShortNameModeDisabled to turn off +// short-name aliasing by default. +// +// TODO: remove this function once we want to default to short-name aliasing. +func getShortNameMode() *types.ShortNameMode { + env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING") + if strings.ToLower(env) == "on" { + return nil // default to whatever registries.conf and c/image decide + } + mode := types.ShortNameModeDisabled + return &mode +} - if options.PullPolicy == PullAlways { +func resolveImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { + if systemContext == nil { + systemContext = &types.SystemContext{} + } + systemContext.ShortNameMode = getShortNameMode() + + fromImage := options.FromImage + // If the image name includes a transport we can use it as it. Special + // treatment for docker references which are subject to pull policies + // that we're handling below. + srcRef, err := alltransports.ParseImageName(options.FromImage) + if err == nil { + if srcRef.Transport().Name() == docker.Transport.Name() { + fromImage = srcRef.DockerReference().String() + } else { pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) - if err != nil { - logrus.Debugf("unable to pull and read image %q: %v", image, err) - failures = append(failures, failure{resolvedImageName: image, err: err}) - continue - } - return pulledReference, transport, pulledImg, nil + return pulledReference, srcRef.Transport().Name(), pulledImg, err } + } - destImage, err := localImageNameForReference(ctx, store, srcRef) - if err != nil { - return nil, "", nil, errors.Wrapf(err, "error computing local image name for %q", transports.ImageName(srcRef)) + localImageRef, _, localImage, err := resolveLocalImage(systemContext, store, options) + if err != nil { + return nil, "", nil, err + } + + // If we could resolve the image locally, check if it was referenced by + // ID. In that case, we don't need to bother any further and can + // prevent prompting the user. + if localImage != nil && strings.HasPrefix(localImage.ID, options.FromImage) { + return localImageRef, localImageRef.Transport().Name(), localImage, nil + } + + if options.PullPolicy == PullNever || options.PullPolicy == PullIfMissing { + if localImage != nil { + return localImageRef, localImageRef.Transport().Name(), localImage, nil } - if destImage == "" { - return nil, "", nil, errors.Errorf("error computing local image name for %q", transports.ImageName(srcRef)) + if options.PullPolicy == PullNever { + return nil, "", nil, errors.Errorf("pull policy is %q but %q could not be found locally", "never", options.FromImage) } - ref, err := is.Transport.ParseStoreReference(store, destImage) + } + + resolved, err := shortnames.Resolve(systemContext, fromImage) + if err != nil { + return nil, "", nil, err + } + + // Print the image-resolution description unless we're looking for a + // new image and already found a local image. In many cases, the + // description will be more confusing than helpful (e.g., `buildah from + // localImage`). + if desc := resolved.Description(); len(desc) > 0 { + logrus.Debug(desc) + if !(options.PullPolicy == PullIfNewer && localImage != nil) { + if options.ReportWriter != nil { + if _, err := options.ReportWriter.Write([]byte(desc + "\n")); err != nil { + return nil, "", nil, err + } + } + } + } + + var pullErrors []error + for _, pullCandidate := range resolved.PullCandidates { + ref, err := docker.NewReference(pullCandidate.Value) if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", destImage) + return nil, "", nil, err } - if options.PullPolicy == PullIfNewer { - img, err := is.Transport.GetStoreImage(store, ref) - if err == nil { - // Let's see if this image is on the repository and if it's there - // then note it's Created date. - var repoImageCreated time.Time - repoImageFound := false - repoImage, err := srcRef.NewImage(ctx, systemContext) - if err == nil { - inspect, err := repoImage.Inspect(ctx) - if err == nil { - repoImageFound = true - repoImageCreated = *inspect.Created - } - repoImage.Close() - } - if !repoImageFound || repoImageCreated == img.Created { - // The image is only local or the same date is on the - // local and repo versions of the image, no need to pull. - return ref, transport, img, nil - } + // We're tasked to pull a "newer" image. If there's no local + // image, we have no base for comparison, so we'll pull the + // first available image. + // + // If there's a local image, the `pullCandidate` is considered + // to be newer if its time stamp differs from the local one. + // Otherwise, we don't pull and skip it. + if options.PullPolicy == PullIfNewer && localImage != nil { + remoteImage, err := ref.NewImage(ctx, systemContext) + if err != nil { + logrus.Debugf("unable to remote-inspect image %q: %v", pullCandidate.Value.String(), err) + pullErrors = append(pullErrors, err) + continue } - } else { - // Get the image from the store if present for PullNever and PullIfMissing - img, err := is.Transport.GetStoreImage(store, ref) - if err == nil { - return ref, transport, img, nil + defer remoteImage.Close() + + remoteData, err := remoteImage.Inspect(ctx) + if err != nil { + logrus.Debugf("unable to remote-inspect image %q: %v", pullCandidate.Value.String(), err) + pullErrors = append(pullErrors, err) + continue } - if errors.Cause(err) == storage.ErrImageUnknown && options.PullPolicy == PullNever { - logrus.Debugf("no such image %q: %v", transports.ImageName(ref), err) - failures = append(failures, failure{ - resolvedImageName: image, - err: errors.Errorf("no such image %q", transports.ImageName(ref)), - }) + + // FIXME: we should compare image digests not time stamps. + // Comparing time stamps is flawed. Be aware that fixing + // it may entail non-trivial changes to the tests. Please + // refer to https://github.com/containers/buildah/issues/2779 + // for more. + if localImage.Created.Equal(*remoteData.Created) { continue } } - pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) + pulledImg, pulledReference, err := pullAndFindImage(ctx, store, ref, options, systemContext) if err != nil { - logrus.Debugf("unable to pull and read image %q: %v", image, err) - failures = append(failures, failure{resolvedImageName: image, err: err}) + logrus.Debugf("unable to pull and read image %q: %v", pullCandidate.Value.String(), err) + pullErrors = append(pullErrors, err) continue } - return pulledReference, transport, pulledImg, nil - } - - if len(failures) != len(candidates) { - return nil, "", nil, errors.Errorf("internal error: %d candidates (%#v) vs. %d failures (%#v)", len(candidates), candidates, len(failures), failures) - } - registriesConfPath := sysregistriesv2.ConfigPath(systemContext) - switch len(failures) { - case 0: - if searchRegistriesWereUsedButEmpty { - return nil, "", nil, errors.Errorf("image name %q is a short name and no search registries are defined in %s.", options.FromImage, registriesConfPath) + // Make sure to record the short-name alias if necessary. + if err = pullCandidate.Record(); err != nil { + return nil, "", nil, err } - return nil, "", nil, errors.Errorf("internal error: no pull candidates were available for %q for an unknown reason", options.FromImage) - case 1: - err := failures[0].err - if failures[0].resolvedImageName != options.FromImage { - err = errors.Wrapf(err, "while pulling %q as %q", options.FromImage, failures[0].resolvedImageName) - } - if searchRegistriesWereUsedButEmpty { - err = errors.Wrapf(err, "(image name %q is a short name and no search registries are defined in %s)", options.FromImage, registriesConfPath) - } - return nil, "", nil, err + return pulledReference, "", pulledImg, nil + } - default: - // NOTE: a multi-line error string: - e := fmt.Sprintf("The following failures happened while trying to pull image specified by %q based on search registries in %s:", options.FromImage, registriesConfPath) - for _, f := range failures { - e = e + fmt.Sprintf("\n* %q: %s", f.resolvedImageName, f.err.Error()) - } - return nil, "", nil, errors.New(e) + // If we were looking for a newer image but could not find one, return + // the local image if present. + if options.PullPolicy == PullIfNewer && localImage != nil { + return localImageRef, localImageRef.Transport().Name(), localImage, nil } + + return nil, "", nil, resolved.FormatPullErrors(pullErrors) } func containerNameExist(name string, containers []storage.Container) bool { diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index af7453c91..62a328de0 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -59,7 +59,6 @@ type BudResults struct { Creds string DisableCompression bool DisableContentTrust bool - DecryptionKeys []string File []string Format string Iidfile string @@ -90,38 +89,39 @@ type BudResults struct { // FromAndBugResults represents the results for common flags // in bud and from type FromAndBudResults struct { - AddHost []string - BlobCache string - CapAdd []string - CapDrop []string - CgroupParent string - CPUPeriod uint64 - CPUQuota int64 - CPUSetCPUs string - CPUSetMems string - CPUShares uint64 - Devices []string - DNSSearch []string - DNSServers []string - DNSOptions []string - HTTPProxy bool - Isolation string - Memory string - MemorySwap string - OverrideArch string - OverrideOS string - SecurityOpt []string - ShmSize string - Ulimit []string - Volumes []string + AddHost []string + BlobCache string + CapAdd []string + CapDrop []string + CgroupParent string + CPUPeriod uint64 + CPUQuota int64 + CPUSetCPUs string + CPUSetMems string + CPUShares uint64 + DecryptionKeys []string + Devices []string + DNSSearch []string + DNSServers []string + DNSOptions []string + HTTPProxy bool + Isolation string + Memory string + MemorySwap string + OverrideArch string + OverrideOS string + SecurityOpt []string + ShmSize string + Ulimit []string + Volumes []string } // GetUserNSFlags returns the common flags for usernamespace func GetUserNSFlags(flags *UserNSResults) pflag.FlagSet { usernsFlags := pflag.FlagSet{} usernsFlags.StringVar(&flags.UserNS, "userns", "", "'container', `path` of user namespace to join, or 'host'") - usernsFlags.StringSliceVar(&flags.UserNSUIDMap, "userns-uid-map", []string{}, "`containerID:hostID:length` UID mapping to use in user namespace") - usernsFlags.StringSliceVar(&flags.UserNSGIDMap, "userns-gid-map", []string{}, "`containerID:hostID:length` GID mapping to use in user namespace") + usernsFlags.StringSliceVar(&flags.UserNSUIDMap, "userns-uid-map", []string{}, "`containerUID:hostUID:length` UID mapping to use in user namespace") + usernsFlags.StringSliceVar(&flags.UserNSGIDMap, "userns-gid-map", []string{}, "`containerGID:hostGID:length` GID mapping to use in user namespace") usernsFlags.StringVar(&flags.UserNSUIDMapUser, "userns-uid-map-user", "", "`name` of entries from /etc/subuid to use to set user namespace UID mapping") usernsFlags.StringVar(&flags.UserNSGIDMapGroup, "userns-gid-map-group", "", "`name` of entries from /etc/subgid to use to set user namespace GID mapping") return usernsFlags @@ -208,6 +208,9 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.StringSliceVar(&flags.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") fs.StringVar(&flags.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") fs.StringVar(&flags.SignaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") + if err := fs.MarkHidden("signature-policy"); err != nil { + panic(fmt.Sprintf("error marking the signature-policy flag as hidden: %v", err)) + } fs.BoolVar(&flags.Squash, "squash", false, "squash newly built layers into a single new layer") fs.StringArrayVarP(&flags.Tag, "tag", "t", []string{}, "tagged `name` to apply to the built image") fs.StringVar(&flags.Target, "target", "", "set the target build stage to build") @@ -265,6 +268,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, fs.Uint64VarP(&flags.CPUShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") fs.StringVar(&flags.CPUSetCPUs, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") fs.StringVar(&flags.CPUSetMems, "cpuset-mems", "", "memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.") + fs.StringSliceVar(&flags.DecryptionKeys, "decryption-key", nil, "key needed to decrypt the image") fs.StringArrayVar(&flags.Devices, "device", defaultContainerConfig.Containers.Devices, "Additional devices to be used within containers (default [])") fs.StringSliceVar(&flags.DNSSearch, "dns-search", defaultContainerConfig.Containers.DNSSearches, "Set custom DNS search domains") fs.StringSliceVar(&flags.DNSServers, "dns", defaultContainerConfig.Containers.DNSServers, "Set custom DNS servers or disable it completely by setting it to 'none', which prevents the automatic creation of `/etc/resolv.conf`.") @@ -308,6 +312,7 @@ func GetFromAndBudFlagsCompletions() commonComp.FlagCompletions { flagCompletion["cpu-shares"] = commonComp.AutocompleteNone flagCompletion["cpuset-cpus"] = commonComp.AutocompleteNone flagCompletion["cpuset-mems"] = commonComp.AutocompleteNone + flagCompletion["decryption-key"] = commonComp.AutocompleteNone flagCompletion["device"] = commonComp.AutocompleteDefault flagCompletion["dns-search"] = commonComp.AutocompleteNone flagCompletion["dns"] = commonComp.AutocompleteNone diff --git a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go index ee2e9a7c8..32f888fa8 100644 --- a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go +++ b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go @@ -38,7 +38,7 @@ type secretData struct { // saveTo saves secret data to given directory func (s secretData) saveTo(dir string) error { path := filepath.Join(dir, s.name) - if err := os.MkdirAll(filepath.Dir(path), s.dirMode); err != nil && !os.IsExist(err) { + if err := os.MkdirAll(filepath.Dir(path), s.dirMode); err != nil { return err } return ioutil.WriteFile(path, s.data, s.mode) diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index bb52ec1ed..d7e7b8890 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -7,7 +7,6 @@ import ( "time" "github.com/containers/buildah/pkg/blobcache" - "github.com/containers/buildah/util" "github.com/containers/image/v5/directory" "github.com/containers/image/v5/docker" dockerarchive "github.com/containers/image/v5/docker/archive" @@ -18,6 +17,7 @@ import ( "github.com/containers/image/v5/signature" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" encconfig "github.com/containers/ocicrypt/config" "github.com/containers/storage" @@ -171,63 +171,63 @@ func Pull(ctx context.Context, imageName string, options PullOptions) (imageID s OciDecryptConfig: options.OciDecryptConfig, } - storageRef, transport, img, err := resolveImage(ctx, systemContext, options.Store, boptions) + if !options.AllTags { + _, _, img, err := resolveImage(ctx, systemContext, options.Store, boptions) + if err != nil { + return "", err + } + return img.ID, nil + } + + srcRef, err := alltransports.ParseImageName(imageName) + if err == nil && srcRef.Transport().Name() != docker.Transport.Name() { + return "", errors.New("Non-docker transport is not supported, for --all-tags pulling") + } + + storageRef, _, _, err := resolveImage(ctx, systemContext, options.Store, boptions) if err != nil { return "", err } var errs *multierror.Error - if options.AllTags { - if transport != util.DefaultTransport { - return "", errors.New("Non-docker transport is not supported, for --all-tags pulling") - } - - repo := reference.TrimNamed(storageRef.DockerReference()) - dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) + repo := reference.TrimNamed(storageRef.DockerReference()) + dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) + if err != nil { + return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) + } + tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) + if err != nil { + return "", errors.Wrapf(err, "error getting repository tags") + } + for _, tag := range tags { + tagged, err := reference.WithTag(repo, tag) if err != nil { - return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) + errs = multierror.Append(errs, err) + continue } - tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) + taggedRef, err := docker.NewReference(tagged) if err != nil { - return "", errors.Wrapf(err, "error getting repository tags") + return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) } - for _, tag := range tags { - tagged, err := reference.WithTag(repo, tag) - if err != nil { - errs = multierror.Append(errs, err) - continue + if options.ReportWriter != nil { + if _, err := options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")); err != nil { + return "", errors.Wrapf(err, "error writing pull report") } - taggedRef, err := docker.NewReference(tagged) - if err != nil { - return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) - } - if options.ReportWriter != nil { - if _, err := options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")); err != nil { - return "", errors.Wrapf(err, "error writing pull report") - } - } - ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - imageID = taggedImg.ID } - } else { - imageID = img.ID - } - if errs == nil { - err = nil - } else { - err = errs.ErrorOrNil() + ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + imageID = taggedImg.ID } - return imageID, err + return imageID, errs.ErrorOrNil() } func pullImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 3a07407b0..d907941ed 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -23,6 +23,7 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containers/buildah/bind" "github.com/containers/buildah/chroot" + "github.com/containers/buildah/copier" "github.com/containers/buildah/pkg/overlay" "github.com/containers/buildah/pkg/secrets" "github.com/containers/buildah/util" @@ -165,11 +166,6 @@ func (b *Builder) Run(command []string, options RunOptions) error { spec := g.Config g = nil - logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd)) - if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil && !os.IsExist(err) { - return err - } - // Set the seccomp configuration using the specified profile name. Some syscalls are // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot), // so we sorted out the capabilities lists first. @@ -184,6 +180,15 @@ func (b *Builder) Run(command []string, options RunOptions) error { } rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)} + mode := os.FileMode(0755) + coptions := copier.MkdirOptions{ + ChownNew: rootIDPair, + ChmodNew: &mode, + } + if err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, spec.Process.Cwd), coptions); err != nil { + return err + } + bindFiles := make(map[string]string) namespaceOptions := append(b.NamespaceOptions, options.NamespaceOptions...) volumes := b.Volumes() @@ -1981,7 +1986,6 @@ func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions } func setupRootlessSpecChanges(spec *specs.Spec, bundleDir string, shmSize string) error { - spec.Hostname = "" spec.Process.User.AdditionalGids = nil spec.Linux.Resources = nil @@ -2137,10 +2141,6 @@ func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) logrus.Debugf("Forcing use of a user namespace.") } options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UserNamespace)}) - if ns := options.NamespaceOptions.Find(string(specs.UTSNamespace)); ns != nil && !ns.Host { - logrus.Debugf("Disabling UTS namespace.") - } - options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UTSNamespace), Host: true}) case IsolationOCI: pidns := options.NamespaceOptions.Find(string(specs.PIDNamespace)) userns := options.NamespaceOptions.Find(string(specs.UserNamespace)) diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 00efc8d21..99f68d9e1 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -12,10 +12,10 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/signature" is "github.com/containers/image/v5/storage" - "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/storage" @@ -69,42 +69,10 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto } } - // If the image includes a transport's name as a prefix, use it as-is. - if strings.HasPrefix(name, DefaultTransport) { - return []string{strings.TrimPrefix(name, DefaultTransport)}, DefaultTransport, false, nil - } - split := strings.SplitN(name, ":", 2) - if StartsWithValidTransport(name) && len(split) == 2 { - if trans := transports.Get(split[0]); trans != nil { - return []string{split[1]}, trans.Name(), false, nil - } - } - // If the image name already included a domain component, we're done. - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, "", false, errors.Wrapf(err, "error parsing image name %q", name) - } - if named.String() == name { - // Parsing produced the same result, so there was a domain name in there to begin with. - return []string{name}, DefaultTransport, false, nil - } - if reference.Domain(named) != "" && RegistryDefaultPathPrefix[reference.Domain(named)] != "" { - // If this domain can cause us to insert something in the middle, check if that happened. - repoPath := reference.Path(named) - domain := reference.Domain(named) - tag := "" - if tagged, ok := named.(reference.Tagged); ok { - tag = ":" + tagged.Tag() - } - digest := "" - if digested, ok := named.(reference.Digested); ok { - digest = "@" + digested.Digest().String() - } - defaultPrefix := RegistryDefaultPathPrefix[reference.Domain(named)] + "/" - if strings.HasPrefix(repoPath, defaultPrefix) && path.Join(domain, repoPath[len(defaultPrefix):])+tag+digest == name { - // Yup, parsing just inserted a bit in the middle, so there was a domain name there to begin with. - return []string{name}, DefaultTransport, false, nil - } + // Transports are not supported for local image look ups. + srcRef, err := alltransports.ParseImageName(name) + if err == nil { + return []string{srcRef.StringWithinTransport()}, srcRef.Transport().Name(), false, nil } // Figure out the list of registries. @@ -126,25 +94,26 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto } searchRegistriesAreEmpty := len(registries) == 0 - // Create all of the combinations. Some registries need an additional component added, so - // use our lookaside map to keep track of them. If there are no configured registries, we'll - // return a name using "localhost" as the registry name. - candidates := []string{} - initRegistries := []string{"localhost"} + var candidates []string + // Set the first registry if requested. if firstRegistry != "" && firstRegistry != "localhost" { - initRegistries = append([]string{firstRegistry}, initRegistries...) - } - for _, registry := range append(initRegistries, registries...) { - if registry == "" { - continue - } middle := "" - if prefix, ok := RegistryDefaultPathPrefix[registry]; ok && !strings.ContainsRune(name, '/') { + if prefix, ok := RegistryDefaultPathPrefix[firstRegistry]; ok && !strings.ContainsRune(name, '/') { middle = prefix } - candidate := path.Join(registry, middle, name) + candidate := path.Join(firstRegistry, middle, name) candidates = append(candidates, candidate) } + + // Local short-name resolution. + namedCandidates, err := shortnames.ResolveLocally(sc, name) + if err != nil { + return nil, "", false, err + } + for _, named := range namedCandidates { + candidates = append(candidates, named.String()) + } + return candidates, DefaultTransport, searchRegistriesAreEmpty, nil } diff --git a/vendor/github.com/containers/buildah/util/util_linux.go b/vendor/github.com/containers/buildah/util/util_linux.go index 1a13699df..cca1f9e7e 100644 --- a/vendor/github.com/containers/buildah/util/util_linux.go +++ b/vendor/github.com/containers/buildah/util/util_linux.go @@ -1,7 +1,6 @@ package util import ( - "os" "syscall" "golang.org/x/sys/unix" @@ -19,11 +18,3 @@ func IsCgroup2UnifiedMode() (bool, error) { }) return isUnified, isUnifiedErr } - -func UID(st os.FileInfo) int { - return int(st.Sys().(*syscall.Stat_t).Uid) -} - -func GID(st os.FileInfo) int { - return int(st.Sys().(*syscall.Stat_t).Gid) -} diff --git a/vendor/github.com/containers/buildah/util/util_unix.go b/vendor/github.com/containers/buildah/util/util_unix.go index 04d9a01cc..29983e40f 100644 --- a/vendor/github.com/containers/buildah/util/util_unix.go +++ b/vendor/github.com/containers/buildah/util/util_unix.go @@ -29,3 +29,11 @@ func (h *HardlinkChecker) Add(fi os.FileInfo, name string) { h.hardlinks.Store(makeHardlinkDeviceAndInode(st), name) } } + +func UID(st os.FileInfo) int { + return int(st.Sys().(*syscall.Stat_t).Uid) +} + +func GID(st os.FileInfo) int { + return int(st.Sys().(*syscall.Stat_t).Gid) +} diff --git a/vendor/github.com/containers/buildah/util/util_unsupported.go b/vendor/github.com/containers/buildah/util/util_unsupported.go index 8810536a6..05a68f60b 100644 --- a/vendor/github.com/containers/buildah/util/util_unsupported.go +++ b/vendor/github.com/containers/buildah/util/util_unsupported.go @@ -2,19 +2,7 @@ package util -import ( - "os" -) - // IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. func IsCgroup2UnifiedMode() (bool, error) { return false, nil } - -func UID(st os.FileInfo) int { - return 0 -} - -func GID(st os.FileInfo) int { - return 0 -} diff --git a/vendor/github.com/containers/buildah/util/util_windows.go b/vendor/github.com/containers/buildah/util/util_windows.go index 0e7f92325..18965ab17 100644 --- a/vendor/github.com/containers/buildah/util/util_windows.go +++ b/vendor/github.com/containers/buildah/util/util_windows.go @@ -14,3 +14,11 @@ func (h *HardlinkChecker) Check(fi os.FileInfo) string { } func (h *HardlinkChecker) Add(fi os.FileInfo, name string) { } + +func UID(st os.FileInfo) int { + return 0 +} + +func GID(st os.FileInfo) int { + return 0 +} diff --git a/vendor/github.com/containers/image/v5/copy/copy.go b/vendor/github.com/containers/image/v5/copy/copy.go index d8e3fa106..4d5b07689 100644 --- a/vendor/github.com/containers/image/v5/copy/copy.go +++ b/vendor/github.com/containers/image/v5/copy/copy.go @@ -1166,6 +1166,21 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc) return digest.Canonical.FromReader(stream) } +// errorAnnotationReader wraps the io.Reader passed to PutBlob for annotating the error happened during read. +// These errors are reported as PutBlob errors, so we would otherwise misleadingly attribute them to the copy destination. +type errorAnnotationReader struct { + reader io.Reader +} + +// Read annotates the error happened during read +func (r errorAnnotationReader) Read(b []byte) (n int, err error) { + n, err = r.reader.Read(b) + if err != io.EOF { + return n, errors.Wrapf(err, "error happened during read") + } + return n, err +} + // copyBlobFromStream copies a blob with srcInfo (with known Digest and Annotations and possibly known Size) from srcStream to dest, // perhaps sending a copy to an io.Writer if getOriginalLayerCopyWriter != nil, // perhaps compressing it if canCompress, @@ -1335,7 +1350,7 @@ func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, sr } // === Finally, send the layer stream to dest. - uploadedInfo, err := c.dest.PutBlob(ctx, destStream, inputInfo, c.blobInfoCache, isConfig) + uploadedInfo, err := c.dest.PutBlob(ctx, &errorAnnotationReader{destStream}, inputInfo, c.blobInfoCache, isConfig) if err != nil { return types.BlobInfo{}, errors.Wrap(err, "Error writing blob") } diff --git a/vendor/github.com/containers/image/v5/docker/docker_image.go b/vendor/github.com/containers/image/v5/docker/docker_image.go index 479effa59..0c1cee0d3 100644 --- a/vendor/github.com/containers/image/v5/docker/docker_image.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image.go @@ -4,12 +4,15 @@ import ( "context" "encoding/json" "fmt" + "net/http" "net/url" "strings" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -103,3 +106,47 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types. } return tags, nil } + +// GetDigest returns the image's digest +// Use this to optimize and avoid use of an ImageSource based on the returned digest; +// if you are going to use an ImageSource anyway, it’s more efficient to create it first +// and compute the digest from the value returned by GetManifest. +// NOTE: Implemented to avoid Docker Hub API limits, and mirror configuration may be +// ignored (but may be implemented in the future) +func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) (digest.Digest, error) { + dr, ok := ref.(dockerReference) + if !ok { + return "", errors.Errorf("ref must be a dockerReference") + } + + tagOrDigest, err := dr.tagOrDigest() + if err != nil { + return "", err + } + + client, err := newDockerClientFromRef(sys, dr, false, "pull") + if err != nil { + return "", errors.Wrap(err, "failed to create client") + } + + path := fmt.Sprintf(manifestPath, reference.Path(dr.ref), tagOrDigest) + headers := map[string][]string{ + "Accept": manifest.DefaultRequestedManifestMIMETypes, + } + + res, err := client.makeRequest(ctx, "HEAD", path, headers, nil, v2Auth, nil) + if err != nil { + return "", err + } + + if res.StatusCode != http.StatusOK { + return "", errors.Wrapf(registryHTTPResponseToError(res), "Error reading digest %s in %s", tagOrDigest, dr.ref.Name()) + } + + dig, err := digest.Parse(res.Header.Get("Docker-Content-Digest")) + if err != nil { + return "", err + } + + return dig, nil +} diff --git a/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go new file mode 100644 index 000000000..e02703d77 --- /dev/null +++ b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go @@ -0,0 +1,458 @@ +package shortnames + +import ( + "fmt" + "os" + "strings" + + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/types" + "github.com/manifoldco/promptui" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "golang.org/x/crypto/ssh/terminal" +) + +// IsShortName returns true if the specified input is a "short name". A "short +// name" refers to a container image without a fully-qualified reference, and +// is hence missing a registry (or domain). Names including a digest are not +// short names. +// +// Examples: +// * short names: "image:tag", "library/fedora" +// * not short names: "quay.io/image", "localhost/image:tag", +// "server.org:5000/lib/image", "image@sha256:..." +func IsShortName(input string) bool { + isShort, _, _ := parseUnnormalizedShortName(input) + return isShort +} + +// parseUnnormalizedShortName parses the input and returns if it's short name, +// the unnormalized reference.Named, and a parsing error. +func parseUnnormalizedShortName(input string) (bool, reference.Named, error) { + ref, err := reference.Parse(input) + if err != nil { + return false, nil, errors.Wrapf(err, "cannot parse input: %q", input) + } + + named, ok := ref.(reference.Named) + if !ok { + return true, nil, errors.Errorf("%q is not a named reference", input) + } + + registry := reference.Domain(named) + if strings.ContainsAny(registry, ".:") || registry == "localhost" { + // A final parse to make sure that docker.io references are correctly + // normalized (e.g., docker.io/alpine to docker.io/library/alpine. + named, err = reference.ParseNormalizedNamed(input) + if err != nil { + return false, nil, errors.Wrapf(err, "cannot normalize input: %q", input) + } + return false, named, nil + } + + return true, named, nil +} + +// splitUserInput parses the user-specified reference. Namely, it strips off +// the tag or digest and stores it in the return values so that both can be +// re-added to a possible resolved alias' or USRs at a later point. +func splitUserInput(named reference.Named) (isTagged bool, isDigested bool, normalized reference.Named, tag string, digest digest.Digest) { + normalized = named + + tagged, isT := named.(reference.NamedTagged) + if isT { + isTagged = true + tag = tagged.Tag() + } + + digested, isD := named.(reference.Digested) + if isD { + isDigested = true + digest = digested.Digest() + } + + // Strip off tag/digest if present. + normalized = reference.TrimNamed(named) + + return +} + +// Add records the specified name-value pair as a new short-name alias to the +// user-specific aliases.conf. It may override an existing alias for `name`. +func Add(ctx *types.SystemContext, name string, value reference.Named) error { + isShort, _, err := parseUnnormalizedShortName(name) + if err != nil { + return err + } + if !isShort { + return errors.Errorf("%q is not a short name", name) + } + return sysregistriesv2.AddShortNameAlias(ctx, name, value.String()) +} + +// Remove clears the short-name alias for the specified name. It throws an +// error in case name does not exist in the machine-generated +// short-name-alias.conf. In such case, the alias must be specified in one of +// the registries.conf files, which is the users' responsibility. +func Remove(ctx *types.SystemContext, name string) error { + isShort, _, err := parseUnnormalizedShortName(name) + if err != nil { + return err + } + if !isShort { + return errors.Errorf("%q is not a short name", name) + } + return sysregistriesv2.RemoveShortNameAlias(ctx, name) +} + +// Resolved encapsulates all data for a resolved image name. +type Resolved struct { + PullCandidates []PullCandidate + + userInput reference.Named + systemContext *types.SystemContext + rationale rationale + originDescription string +} + +func (r *Resolved) addCandidate(named reference.Named) { + r.PullCandidates = append(r.PullCandidates, PullCandidate{named, false, r}) +} + +func (r *Resolved) addCandidateToRecord(named reference.Named) { + r.PullCandidates = append(r.PullCandidates, PullCandidate{named, true, r}) +} + +// Allows to reason over pull errors and add some context information. +// Used in (*Resolved).WrapPullError. +type rationale int + +const ( + // No additional context. + rationaleNone rationale = iota + // Resolved value is a short-name alias. + rationaleAlias + // Resolved value has been completed with an Unqualified Search Registry. + rationaleUSR + // Resolved value has been selected by the user (via the prompt). + rationaleUserSelection +) + +// Description returns a human-readable description about the resolution +// process (e.g., short-name alias, unqualified-search registries, etc.). +// It is meant to be printed before attempting to pull the pull candidates +// to make the short-name resolution more transparent to user. +// +// If the returned string is empty, it is not meant to be printed. +func (r *Resolved) Description() string { + switch r.rationale { + case rationaleAlias: + return fmt.Sprintf("Resolved short name %q to a recorded short-name alias (origin: %s)", r.userInput, r.originDescription) + case rationaleUSR: + return fmt.Sprintf("Completed short name %q with unqualified-search registries (origin: %s)", r.userInput, r.originDescription) + case rationaleUserSelection, rationaleNone: + fallthrough + default: + return "" + } +} + +// FormatPullErrors is a convenience function to format errors that occurred +// while trying to pull all of the resolved pull candidates. +// +// Note that nil is returned if len(pullErrors) == 0. Otherwise, the amount of +// pull errors must equal the amount of pull candidates. +func (r *Resolved) FormatPullErrors(pullErrors []error) error { + if len(pullErrors) >= 0 && len(pullErrors) != len(r.PullCandidates) { + pullErrors = append(pullErrors, + errors.Errorf("internal error: expected %d instead of %d errors for %d pull candidates", + len(r.PullCandidates), len(pullErrors), len(r.PullCandidates))) + } + + switch len(pullErrors) { + case 0: + return nil + case 1: + return pullErrors[0] + default: + var sb strings.Builder + sb.WriteString(fmt.Sprintf("%d errors occurred while pulling:", len(pullErrors))) + for _, e := range pullErrors { + sb.WriteString("\n * ") + sb.WriteString(e.Error()) + } + return errors.New(sb.String()) + } +} + +// PullCandidate is a resolved name. Once the Value has been used +// successfully, users MUST call `(*PullCandidate).Record(..)` to possibly +// record it as a new short-name alias. +type PullCandidate struct { + // Fully-qualified reference with tag or digest. + Value reference.Named + // Control whether to record it permanently as an alias. + record bool + + // Backwards pointer to the Resolved "parent". + resolved *Resolved +} + +// Record may store a short-name alias for the PullCandidate. +func (c *PullCandidate) Record() error { + if !c.record { + return nil + } + + // Strip off tags/digests from name/value. + name := reference.TrimNamed(c.resolved.userInput) + value := reference.TrimNamed(c.Value) + + if err := Add(c.resolved.systemContext, name.String(), value); err != nil { + return errors.Wrapf(err, "error recording short-name alias (%q=%q)", c.resolved.userInput, c.Value) + } + return nil +} + +// Resolve resolves the specified name to either one or more fully-qualified +// image references that the short name may be *pulled* from. If the specified +// name is already a fully-qualified reference (i.e., not a short name), it is +// returned as is. In case, it's a short name, it's resolved according to the +// ShortNameMode in the SystemContext (if specified) or in the registries.conf. +// +// Note that tags and digests are stripped from the specified name before +// looking up an alias. Stripped off tags and digests are later on appended to +// all candidates. If neither tag nor digest is specified, candidates are +// normalized with the "latest" tag. PullCandidates in the returned value may +// be empty if there is no matching alias and no unqualified-search registries +// are configured. +// +// Note that callers *must* call `(PullCandidate).Record` after a returned +// item has been pulled successfully; this callback will record a new +// short-name alias (depending on the specified short-name mode). +// +// Furthermore, before attempting to pull callers *should* call +// `(Resolved).Description` and afterwards use +// `(Resolved).FormatPullErrors` in case of pull errors. +func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { + resolved := &Resolved{} + + // Create a copy of the system context to make it usable beyond this + // function call. + var sys *types.SystemContext + if ctx != nil { + sys = &(*ctx) + } + resolved.systemContext = ctx + + // Detect which mode we're running in. + mode, err := sysregistriesv2.GetShortNameMode(sys) + if err != nil { + return nil, err + } + + // Sanity check the short-name mode. + switch mode { + case types.ShortNameModeDisabled, types.ShortNameModePermissive, types.ShortNameModeEnforcing: + // We're good. + default: + return nil, errors.Errorf("unsupported short-name mode (%v)", mode) + } + + isShort, shortRef, err := parseUnnormalizedShortName(name) + if err != nil { + return nil, err + } + if !isShort { // no short name + named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed + resolved.addCandidate(named) + return resolved, nil + } + + // Strip off the tag to normalize the short name for looking it up in + // the config files. + isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef) + resolved.userInput = shortNameRepo + + // If there's already an alias, use it. + namedAlias, aliasOriginDescription, err := sysregistriesv2.ResolveShortNameAlias(sys, shortNameRepo.String()) + if err != nil { + return nil, err + } + + // Always use an alias if present. + if namedAlias != nil { + if isTagged { + namedAlias, err = reference.WithTag(namedAlias, tag) + if err != nil { + return nil, err + } + } + if isDigested { + namedAlias, err = reference.WithDigest(namedAlias, digest) + if err != nil { + return nil, err + } + } + // Make sure to add ":latest" if needed + namedAlias = reference.TagNameOnly(namedAlias) + + resolved.addCandidate(namedAlias) + resolved.rationale = rationaleAlias + resolved.originDescription = aliasOriginDescription + return resolved, nil + } + + resolved.rationale = rationaleUSR + + // Query the registry for unqualified-search registries. + unqualifiedSearchRegistries, usrConfig, err := sysregistriesv2.UnqualifiedSearchRegistriesWithOrigin(sys) + if err != nil { + return nil, err + } + resolved.originDescription = usrConfig + + for _, reg := range unqualifiedSearchRegistries { + named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name)) + if err != nil { + return nil, errors.Wrapf(err, "error creating reference with unqualified-search registry %q", reg) + } + // Make sure to add ":latest" if needed + named = reference.TagNameOnly(named) + + resolved.addCandidate(named) + } + + // If we're running in disabled, return the candidates without + // prompting (and without recording). + if mode == types.ShortNameModeDisabled { + return resolved, nil + } + + // If we have only one candidate, there's no ambiguity. In case of an + // empty candidate slices, callers can implement custom logic or raise + // an error. + if len(resolved.PullCandidates) <= 1 { + return resolved, nil + } + + // If we don't have a TTY, act according to the mode. + if !terminal.IsTerminal(int(os.Stdout.Fd())) || !terminal.IsTerminal(int(os.Stdin.Fd())) { + switch mode { + case types.ShortNameModePermissive: + // Permissive falls back to using all candidates. + return resolved, nil + case types.ShortNameModeEnforcing: + // Enforcing errors out without a prompt. + return nil, errors.New("short-name resolution enforced but cannot prompt without a TTY") + default: + // We should not end up here. + return nil, errors.Errorf("unexpected short-name mode (%v) during resolution", mode) + } + } + + // We have a TTY, and can prompt the user with a selection of all + // possible candidates. + strCandidates := []string{} + for _, candidate := range resolved.PullCandidates { + strCandidates = append(strCandidates, candidate.Value.String()) + } + prompt := promptui.Select{ + Label: "Please select an image", + Items: strCandidates, + HideHelp: true, // do not show navigation help + } + + _, selection, err := prompt.Run() + if err != nil { + return nil, err + } + + named, err := reference.ParseNormalizedNamed(selection) + if err != nil { + return nil, errors.Wrapf(err, "selection %q is not a valid reference", selection) + } + + resolved.PullCandidates = nil + resolved.addCandidateToRecord(named) + resolved.rationale = rationaleUserSelection + + return resolved, nil +} + +// ResolveLocally resolves the specified name to either one or more local +// images. If the specified name is already a fully-qualified reference (i.e., +// not a short name), it is returned as is. In case, it's a short name, the +// returned slice of named references looks as follows: +// +// 1) If present, the short-name alias +// 2) "localhost/" as used by many container engines such as Podman and Buildah +// 3) Unqualified-search registries from the registries.conf files +// +// Note that tags and digests are stripped from the specified name before +// looking up an alias. Stripped off tags and digests are later on appended to +// all candidates. If neither tag nor digest is specified, candidates are +// normalized with the "latest" tag. The returned slice contains at least one +// item. +func ResolveLocally(ctx *types.SystemContext, name string) ([]reference.Named, error) { + isShort, shortRef, err := parseUnnormalizedShortName(name) + if err != nil { + return nil, err + } + if !isShort { // no short name + named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed + return []reference.Named{named}, nil + } + + var candidates []reference.Named + + // Strip off the tag to normalize the short name for looking it up in + // the config files. + isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef) + + // If there's already an alias, use it. + namedAlias, _, err := sysregistriesv2.ResolveShortNameAlias(ctx, shortNameRepo.String()) + if err != nil { + return nil, err + } + if namedAlias != nil { + if isTagged { + namedAlias, err = reference.WithTag(namedAlias, tag) + if err != nil { + return nil, err + } + } + if isDigested { + namedAlias, err = reference.WithDigest(namedAlias, digest) + if err != nil { + return nil, err + } + } + // Make sure to add ":latest" if needed + namedAlias = reference.TagNameOnly(namedAlias) + + candidates = append(candidates, namedAlias) + } + + // Query the registry for unqualified-search registries. + unqualifiedSearchRegistries, err := sysregistriesv2.UnqualifiedSearchRegistries(ctx) + if err != nil { + return nil, err + } + + // Note that "localhost" has precedence over the unqualified-search registries. + for _, reg := range append([]string{"localhost"}, unqualifiedSearchRegistries...) { + named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name)) + if err != nil { + return nil, errors.Wrapf(err, "error creating reference with unqualified-search registry %q", reg) + } + // Make sure to add ":latest" if needed + named = reference.TagNameOnly(named) + + candidates = append(candidates, named) + } + + return candidates, nil +} diff --git a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go new file mode 100644 index 000000000..fadfe1a35 --- /dev/null +++ b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go @@ -0,0 +1,328 @@ +package sysregistriesv2 + +import ( + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" + "github.com/containers/storage/pkg/lockfile" + "github.com/docker/docker/pkg/homedir" + "github.com/pkg/errors" +) + +// defaultShortNameMode is the default mode of registries.conf files if the +// corresponding field is left empty. +const defaultShortNameMode = types.ShortNameModePermissive + +// userShortNamesFile is the user-specific config file to store aliases. +var userShortNamesFile = filepath.FromSlash("containers/short-name-aliases.conf") + +// shortNameAliasesConfPath returns the path to the machine-generated +// short-name-aliases.conf file. +func shortNameAliasesConfPath(ctx *types.SystemContext) (string, error) { + if ctx != nil && len(ctx.UserShortNameAliasConfPath) > 0 { + return ctx.UserShortNameAliasConfPath, nil + } + + configHome, err := homedir.GetConfigHome() + if err != nil { + return "", err + } + + return filepath.Join(configHome, userShortNamesFile), nil +} + +// shortNameAliasConf is a subset of the `V2RegistriesConf` format. It's used in the +// software-maintained `userShortNamesFile`. +type shortNameAliasConf struct { + // A map for aliasing short names to their fully-qualified image + // reference counter parts. + // Note that Aliases is niled after being loaded from a file. + Aliases map[string]string `toml:"aliases"` +} + +// alias combines the parsed value of an alias with the config file it has been +// specified in. The config file is crucial for an improved user experience +// such that users are able to resolve potential pull errors. +type alias struct { + // The parsed value of an alias. May be nil if set to "" in a config. + value reference.Named + // The config file the alias originates from. + configOrigin string +} + +// shortNameAliasCache is the result of parsing shortNameAliasConf, +// pre-processed for faster usage. +type shortNameAliasCache struct { + // Note that an alias value may be nil iff it's set as an empty string + // in the config. + namedAliases map[string]alias +} + +// ResolveShortNameAlias performs an alias resolution of the specified name. +// The user-specific short-name-aliases.conf has precedence over aliases in the +// assembled registries.conf. It returns the possibly resolved alias or nil, a +// human-readable description of the config where the alias is specified, and +// an error. The origin of the config file is crucial for an improved user +// experience such that users are able to resolve potential pull errors. +// Almost all callers should use pkg/shortnames instead. +// +// Note that it’s the caller’s responsibility to pass only a repository +// (reference.IsNameOnly) as the short name. +func ResolveShortNameAlias(ctx *types.SystemContext, name string) (reference.Named, string, error) { + if err := validateShortName(name); err != nil { + return nil, "", err + } + confPath, lock, err := shortNameAliasesConfPathAndLock(ctx) + if err != nil { + return nil, "", err + } + + // Acquire the lock as a reader to allow for multiple routines in the + // same process space to read simultaneously. + lock.RLock() + defer lock.Unlock() + + _, aliasCache, err := loadShortNameAliasConf(confPath) + if err != nil { + return nil, "", err + } + + // First look up the short-name-aliases.conf. Note that a value may be + // nil iff it's set as an empty string in the config. + alias, resolved := aliasCache.namedAliases[name] + if resolved { + return alias.value, alias.configOrigin, nil + } + + config, err := getConfig(ctx) + if err != nil { + return nil, "", err + } + alias, resolved = config.aliasCache.namedAliases[name] + if resolved { + return alias.value, alias.configOrigin, nil + } + return nil, "", nil +} + +// editShortNameAlias loads the aliases.conf file and changes it. If value is +// set, it adds the name-value pair as a new alias. Otherwise, it will remove +// name from the config. +func editShortNameAlias(ctx *types.SystemContext, name string, value *string) error { + if err := validateShortName(name); err != nil { + return err + } + if value != nil { + if _, err := parseShortNameValue(*value); err != nil { + return err + } + } + + confPath, lock, err := shortNameAliasesConfPathAndLock(ctx) + if err != nil { + return err + } + + // Acquire the lock as a writer to prevent data corruption. + lock.Lock() + defer lock.Unlock() + + // Load the short-name-alias.conf, add the specified name-value pair, + // and write it back to the file. + conf, _, err := loadShortNameAliasConf(confPath) + if err != nil { + return err + } + + if conf.Aliases == nil { // Ensure we have a map to update. + conf.Aliases = make(map[string]string) + } + if value != nil { + conf.Aliases[name] = *value + } else { + // If the name does not exist, throw an error. + if _, exists := conf.Aliases[name]; !exists { + return errors.Errorf("short-name alias %q not found in %q: please check registries.conf files", name, confPath) + } + + delete(conf.Aliases, name) + } + + f, err := os.OpenFile(confPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer f.Close() + + encoder := toml.NewEncoder(f) + return encoder.Encode(conf) +} + +// AddShortNameAlias adds the specified name-value pair as a new alias to the +// user-specific aliases.conf. It may override an existing alias for `name`. +// +// Note that it’s the caller’s responsibility to pass only a repository +// (reference.IsNameOnly) as the short name. +func AddShortNameAlias(ctx *types.SystemContext, name string, value string) error { + return editShortNameAlias(ctx, name, &value) +} + +// RemoveShortNameAlias clears the alias for the specified name. It throws an +// error in case name does not exist in the machine-generated +// short-name-alias.conf. In such case, the alias must be specified in one of +// the registries.conf files, which is the users' responsibility. +// +// Note that it’s the caller’s responsibility to pass only a repository +// (reference.IsNameOnly) as the short name. +func RemoveShortNameAlias(ctx *types.SystemContext, name string) error { + return editShortNameAlias(ctx, name, nil) +} + +// parseShortNameValue parses the specified alias into a reference.Named. The alias is +// expected to not be tagged or carry a digest and *must* include a +// domain/registry. +// +// Note that the returned reference is always normalized. +func parseShortNameValue(alias string) (reference.Named, error) { + ref, err := reference.Parse(alias) + if err != nil { + return nil, errors.Wrapf(err, "error parsing alias %q", alias) + } + + if _, ok := ref.(reference.Digested); ok { + return nil, errors.Errorf("invalid alias %q: must not contain digest", alias) + } + + if _, ok := ref.(reference.Tagged); ok { + return nil, errors.Errorf("invalid alias %q: must not contain tag", alias) + } + + named, ok := ref.(reference.Named) + if !ok { + return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias) + } + + registry := reference.Domain(named) + if !(strings.ContainsAny(registry, ".:") || registry == "localhost") { + return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias) + } + + // A final parse to make sure that docker.io references are correctly + // normalized (e.g., docker.io/alpine to docker.io/library/alpine. + named, err = reference.ParseNormalizedNamed(alias) + return named, err +} + +// validateShortName parses the specified `name` of an alias (i.e., the left-hand +// side) and checks if it's a short name and does not include a tag or digest. +func validateShortName(name string) error { + repo, err := reference.Parse(name) + if err != nil { + return errors.Wrapf(err, "cannot parse short name: %q", name) + } + + if _, ok := repo.(reference.Digested); ok { + return errors.Errorf("invalid short name %q: must not contain digest", name) + } + + if _, ok := repo.(reference.Tagged); ok { + return errors.Errorf("invalid short name %q: must not contain tag", name) + } + + named, ok := repo.(reference.Named) + if !ok { + return errors.Errorf("invalid short name %q: no name", name) + } + + registry := reference.Domain(named) + if strings.ContainsAny(registry, ".:") || registry == "localhost" { + return errors.Errorf("invalid short name %q: must not contain registry", name) + } + return nil +} + +// newShortNameAliasCache parses shortNameAliasConf and returns the corresponding internal +// representation. +func newShortNameAliasCache(path string, conf *shortNameAliasConf) (*shortNameAliasCache, error) { + res := shortNameAliasCache{ + namedAliases: make(map[string]alias), + } + errs := []error{} + for name, value := range conf.Aliases { + if err := validateShortName(name); err != nil { + errs = append(errs, err) + } + + // Empty right-hand side values in config files allow to reset + // an alias in a previously loaded config. This way, drop-in + // config files from registries.conf.d can reset potentially + // malconfigured aliases. + if value == "" { + res.namedAliases[name] = alias{nil, path} + continue + } + + named, err := parseShortNameValue(value) + if err != nil { + // We want to report *all* malformed entries to avoid a + // whack-a-mole for the user. + errs = append(errs, err) + } else { + res.namedAliases[name] = alias{named, path} + } + } + if len(errs) > 0 { + err := errs[0] + for i := 1; i < len(errs); i++ { + err = errors.Wrapf(err, "%v\n", errs[i]) + } + return nil, err + } + return &res, nil +} + +// updateWithConfigurationFrom updates c with configuration from updates. +// In case of conflict, updates is preferred. +func (c *shortNameAliasCache) updateWithConfigurationFrom(updates *shortNameAliasCache) { + for name, value := range updates.namedAliases { + c.namedAliases[name] = value + } +} + +func loadShortNameAliasConf(confPath string) (*shortNameAliasConf, *shortNameAliasCache, error) { + conf := shortNameAliasConf{} + + _, err := toml.DecodeFile(confPath, &conf) + if err != nil && !os.IsNotExist(err) { + // It's okay if the config doesn't exist. Other errors are not. + return nil, nil, errors.Wrapf(err, "error loading short-name aliases config file %q", confPath) + } + + // Even if we don’t always need the cache, doing so validates the machine-generated config. The + // file could still be corrupted by another process or user. + cache, err := newShortNameAliasCache(confPath, &conf) + if err != nil { + return nil, nil, errors.Wrapf(err, "error loading short-name aliases config file %q", confPath) + } + + return &conf, cache, nil +} + +func shortNameAliasesConfPathAndLock(ctx *types.SystemContext) (string, lockfile.Locker, error) { + shortNameAliasesConfPath, err := shortNameAliasesConfPath(ctx) + if err != nil { + return "", nil, err + } + // Make sure the path to file exists. + if err := os.MkdirAll(filepath.Dir(shortNameAliasesConfPath), 0700); err != nil { + return "", nil, err + } + + lockPath := shortNameAliasesConfPath + ".lock" + locker, err := lockfile.GetLockfile(lockPath) + return shortNameAliasesConfPath, locker, err +} diff --git a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go index ea2b21575..89ad7c533 100644 --- a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go +++ b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go @@ -154,6 +154,19 @@ type V2RegistriesConf struct { Registries []Registry `toml:"registry"` // An array of host[:port] (not prefix!) entries to use for resolving unqualified image references UnqualifiedSearchRegistries []string `toml:"unqualified-search-registries"` + + // ShortNameMode defines how short-name resolution should be handled by + // _consumers_ of this package. Depending on the mode, the user should + // be prompted with a choice of using one of the unqualified-search + // registries when referring to a short name. + // + // Valid modes are: * "prompt": prompt if stdout is a TTY, otherwise + // use all unqualified-search registries * "enforcing": always prompt + // and error if stdout is not a TTY * "disabled": do not prompt and + // potentially use all unqualified-search registries + ShortNameMode string `toml:"short-name-mode"` + + shortNameAliasConf } // Nonempty returns true if config contains at least one configuration entry. @@ -162,10 +175,23 @@ func (config *V2RegistriesConf) Nonempty() bool { len(config.UnqualifiedSearchRegistries) != 0) } -// tomlConfig is the data type used to unmarshal the toml config. -type tomlConfig struct { - V2RegistriesConf - V1RegistriesConf // for backwards compatibility with sysregistries v1 +// parsedConfig is the result of parsing, and possibly merging, configuration files; +// it is the boundary between the process of reading+ingesting the files, and +// later interpreting the configuraiton based on caller’s requests. +type parsedConfig struct { + // NOTE: Update also parsedConfig.updateWithConfigurationFrom! + + // partialV2 must continue to exist to maintain the return value of TryUpdatingCache + // for compatibility with existing callers. + // We store the authoritative Registries and UnqualifiedSearchRegistries values there as well. + partialV2 V2RegistriesConf + // Absolute path to the configuration file that set the UnqualifiedSearchRegistries. + unqualifiedSearchRegistriesOrigin string + // Result of parsing of partialV2.ShortNameMode. + // NOTE: May be ShortNameModeInvalid to represent ShortNameMode == "" in intermediate values; + // the full configuration in configCache / getConfig() always contains a valid value. + shortNameMode types.ShortNameMode + aliasCache *shortNameAliasCache } // InvalidRegistries represents an invalid registry configurations. An example @@ -254,7 +280,7 @@ var anchoredDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.Strin // postProcess checks the consistency of all the configuration, looks for conflicts, // and normalizes the configuration (e.g., sets the Prefix to Location if not set). -func (config *V2RegistriesConf) postProcess() error { +func (config *V2RegistriesConf) postProcessRegistries() error { regMap := make(map[string][]*Registry) for i := range config.Registries { @@ -301,6 +327,7 @@ func (config *V2RegistriesConf) postProcess() error { msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'insecure' setting", reg.Location) return &InvalidRegistries{s: msg} } + if reg.Blocked != other.Blocked { msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'blocked' setting", reg.Location) return &InvalidRegistries{s: msg} @@ -323,16 +350,25 @@ func (config *V2RegistriesConf) postProcess() error { // rendering later items with the same prefix non-existent. We cannot error // out anymore as this might break existing users, so let's just ignore them // to guarantee that the same prefix exists only once. - knownPrefixes := make(map[string]bool) - uniqueRegistries := []Registry{} + // + // As a side effect of parsedConfig.updateWithConfigurationFrom, the Registries slice + // is always sorted. To be consistent in situations where it is not called (no drop-ins), + // sort it here as well. + prefixes := []string{} + uniqueRegistries := make(map[string]Registry) for i := range config.Registries { // TODO: should we warn if we see the same prefix being used multiple times? - if _, exists := knownPrefixes[config.Registries[i].Prefix]; !exists { - knownPrefixes[config.Registries[i].Prefix] = true - uniqueRegistries = append(uniqueRegistries, config.Registries[i]) + prefix := config.Registries[i].Prefix + if _, exists := uniqueRegistries[prefix]; !exists { + uniqueRegistries[prefix] = config.Registries[i] + prefixes = append(prefixes, prefix) } } - config.Registries = uniqueRegistries + sort.Strings(prefixes) + config.Registries = []Registry{} + for _, prefix := range prefixes { + config.Registries = append(config.Registries, uniqueRegistries[prefix]) + } return nil } @@ -385,6 +421,7 @@ func newConfigWrapper(ctx *types.SystemContext) configWrapper { } else { wrapper.userConfigDirPath = userRegistriesDirPath } + return wrapper } else if ctx != nil && ctx.RootForImplicitAbsolutePaths != "" { wrapper.configPath = filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesConfPath) @@ -426,7 +463,7 @@ var configMutex = sync.Mutex{} // configCache caches already loaded configs with config paths as keys and is // used to avoid redundantly parsing configs. Concurrent accesses to the cache // are synchronized via configMutex. -var configCache = make(map[configWrapper]*V2RegistriesConf) +var configCache = make(map[configWrapper]*parsedConfig) // InvalidateCache invalidates the registry cache. This function is meant to be // used for long-running processes that need to reload potential changes made to @@ -434,11 +471,11 @@ var configCache = make(map[configWrapper]*V2RegistriesConf) func InvalidateCache() { configMutex.Lock() defer configMutex.Unlock() - configCache = make(map[configWrapper]*V2RegistriesConf) + configCache = make(map[configWrapper]*parsedConfig) } // getConfig returns the config object corresponding to ctx, loading it if it is not yet cached. -func getConfig(ctx *types.SystemContext) (*V2RegistriesConf, error) { +func getConfig(ctx *types.SystemContext) (*parsedConfig, error) { wrapper := newConfigWrapper(ctx) configMutex.Lock() if config, inCache := configCache[wrapper]; inCache { @@ -504,27 +541,37 @@ func dropInConfigs(wrapper configWrapper) ([]string, error) { // TryUpdatingCache loads the configuration from the provided `SystemContext` // without using the internal cache. On success, the loaded configuration will // be added into the internal registry cache. +// It returns the resulting configuration; this is DEPRECATED and may not correctly +// reflect any future data handled by this package. func TryUpdatingCache(ctx *types.SystemContext) (*V2RegistriesConf, error) { - return tryUpdatingCache(ctx, newConfigWrapper(ctx)) + config, err := tryUpdatingCache(ctx, newConfigWrapper(ctx)) + if err != nil { + return nil, err + } + return &config.partialV2, err } // tryUpdatingCache implements TryUpdatingCache with an additional configWrapper // argument to avoid redundantly calculating the config paths. -func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*V2RegistriesConf, error) { +func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*parsedConfig, error) { configMutex.Lock() defer configMutex.Unlock() // load the config - config := &tomlConfig{} - if err := config.loadConfig(wrapper.configPath, false); err != nil { + config, err := loadConfigFile(wrapper.configPath, false) + if err != nil { // Continue with an empty []Registry if we use the default config, which // implies that the config path of the SystemContext isn't set. // // Note: if ctx.SystemRegistriesConfPath points to the default config, // we will still return an error. if os.IsNotExist(err) && (ctx == nil || ctx.SystemRegistriesConfPath == "") { - config = &tomlConfig{} - config.V2RegistriesConf = V2RegistriesConf{Registries: []Registry{}} + config = &parsedConfig{} + config.partialV2 = V2RegistriesConf{Registries: []Registry{}} + config.aliasCache, err = newShortNameAliasCache("", &shortNameAliasConf{}) + if err != nil { + return nil, err // Should never happen + } } else { return nil, errors.Wrapf(err, "error loading registries configuration %q", wrapper.configPath) } @@ -537,16 +584,20 @@ func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*V2Regis } for _, path := range dinConfigs { // Enforce v2 format for drop-in-configs. - if err := config.loadConfig(path, true); err != nil { + dropIn, err := loadConfigFile(path, true) + if err != nil { return nil, errors.Wrapf(err, "error loading drop-in registries configuration %q", path) } + config.updateWithConfigurationFrom(dropIn) } - v2Config := &config.V2RegistriesConf + if config.shortNameMode == types.ShortNameModeInvalid { + config.shortNameMode = defaultShortNameMode + } // populate the cache - configCache[wrapper] = v2Config - return v2Config, nil + configCache[wrapper] = config + return config, nil } // GetRegistries loads and returns the registries specified in the config. @@ -557,17 +608,53 @@ func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { if err != nil { return nil, err } - return config.Registries, nil + return config.partialV2.Registries, nil } // UnqualifiedSearchRegistries returns a list of host[:port] entries to try // for unqualified image search, in the returned order) func UnqualifiedSearchRegistries(ctx *types.SystemContext) ([]string, error) { + registries, _, err := UnqualifiedSearchRegistriesWithOrigin(ctx) + return registries, err +} + +// UnqualifiedSearchRegistriesWithOrigin returns a list of host[:port] entries +// to try for unqualified image search, in the returned order. It also returns +// a human-readable description of where these entries are specified (e.g., a +// registries.conf file). +func UnqualifiedSearchRegistriesWithOrigin(ctx *types.SystemContext) ([]string, string, error) { config, err := getConfig(ctx) if err != nil { - return nil, err + return nil, "", err } - return config.UnqualifiedSearchRegistries, nil + return config.partialV2.UnqualifiedSearchRegistries, config.unqualifiedSearchRegistriesOrigin, nil +} + +// parseShortNameMode translates the string into well-typed +// types.ShortNameMode. +func parseShortNameMode(mode string) (types.ShortNameMode, error) { + switch mode { + case "disabled": + return types.ShortNameModeDisabled, nil + case "enforcing": + return types.ShortNameModeEnforcing, nil + case "permissive": + return types.ShortNameModePermissive, nil + default: + return types.ShortNameModeInvalid, errors.Errorf("invalid short-name mode: %q", mode) + } +} + +// GetShortNameMode returns the configured types.ShortNameMode. +func GetShortNameMode(ctx *types.SystemContext) (types.ShortNameMode, error) { + if ctx != nil && ctx.ShortNameMode != nil { + return *ctx.ShortNameMode, nil + } + config, err := getConfig(ctx) + if err != nil { + return -1, err + } + return config.shortNameMode, err } // refMatchesPrefix returns true iff ref, @@ -609,7 +696,7 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) { reg := Registry{} prefixLen := 0 - for _, r := range config.Registries { + for _, r := range config.partialV2.Registries { if refMatchesPrefix(ref, r.Prefix) { length := len(r.Prefix) if length > prefixLen { @@ -624,55 +711,87 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) { return nil, nil } -// loadConfig loads and unmarshals the configuration at the specified path. Note -// that v1 configs are translated into v2 and are cleared. Use forceV2 if the -// config must in the v2 format. -// -// Note that specified fields in path will replace already set fields in the -// tomlConfig. Only the [[registry]] tables are merged by prefix. -func (c *tomlConfig) loadConfig(path string, forceV2 bool) error { +// loadConfigFile loads and unmarshals a single config file. +// Use forceV2 if the config must in the v2 format. +func loadConfigFile(path string, forceV2 bool) (*parsedConfig, error) { logrus.Debugf("Loading registries configuration %q", path) - // Save the registries before decoding the file where they could be lost. - // We merge them later again. - registryMap := make(map[string]Registry) - for i := range c.Registries { - registryMap[c.Registries[i].Prefix] = c.Registries[i] + // tomlConfig allows us to unmarshal either V1 or V2 simultaneously. + type tomlConfig struct { + V2RegistriesConf + V1RegistriesConf // for backwards compatibility with sysregistries v1 } // Load the tomlConfig. Note that `DecodeFile` will overwrite set fields. - c.Registries = nil // important to clear the memory to prevent us from overlapping fields - _, err := toml.DecodeFile(path, c) + var combinedTOML tomlConfig + _, err := toml.DecodeFile(path, &combinedTOML) if err != nil { - return err + return nil, err } - if c.V1RegistriesConf.Nonempty() { + if combinedTOML.V1RegistriesConf.Nonempty() { // Enforce the v2 format if requested. if forceV2 { - return &InvalidRegistries{s: "registry must be in v2 format but is in v1"} + return nil, &InvalidRegistries{s: "registry must be in v2 format but is in v1"} } // Convert a v1 config into a v2 config. - if c.V2RegistriesConf.Nonempty() { - return &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"} + if combinedTOML.V2RegistriesConf.Nonempty() { + return nil, &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"} } - v2, err := c.V1RegistriesConf.ConvertToV2() + converted, err := combinedTOML.V1RegistriesConf.ConvertToV2() if err != nil { - return err + return nil, err } - c.V1RegistriesConf = V1RegistriesConf{} - c.V2RegistriesConf = *v2 + combinedTOML.V1RegistriesConf = V1RegistriesConf{} + combinedTOML.V2RegistriesConf = *converted } + res := parsedConfig{partialV2: combinedTOML.V2RegistriesConf} + // Post process registries, set the correct prefixes, sanity checks, etc. - if err := c.postProcess(); err != nil { - return err + if err := res.partialV2.postProcessRegistries(); err != nil { + return nil, err } + res.unqualifiedSearchRegistriesOrigin = path + + if len(res.partialV2.ShortNameMode) > 0 { + mode, err := parseShortNameMode(res.partialV2.ShortNameMode) + if err != nil { + return nil, err + } + res.shortNameMode = mode + } else { + res.shortNameMode = types.ShortNameModeInvalid + } + + // Parse and validate short-name aliases. + cache, err := newShortNameAliasCache(path, &res.partialV2.shortNameAliasConf) + if err != nil { + return nil, errors.Wrap(err, "error validating short-name aliases") + } + res.aliasCache = cache + // Clear conf.partialV2.shortNameAliasConf to make it available for garbage collection and + // reduce memory consumption. We're consulting aliasCache for lookups. + res.partialV2.shortNameAliasConf = shortNameAliasConf{} + + return &res, nil +} + +// updateWithConfigurationFrom updates c with configuration from updates. +// +// Fields present in updates will typically replace already set fields in c. +// The [[registry]] and alias tables are merged. +func (c *parsedConfig) updateWithConfigurationFrom(updates *parsedConfig) { + // == Merge Registries: + registryMap := make(map[string]Registry) + for i := range c.partialV2.Registries { + registryMap[c.partialV2.Registries[i].Prefix] = c.partialV2.Registries[i] + } // Merge the freshly loaded registries. - for i := range c.Registries { - registryMap[c.Registries[i].Prefix] = c.Registries[i] + for i := range updates.partialV2.Registries { + registryMap[updates.partialV2.Registries[i].Prefix] = updates.partialV2.Registries[i] } // Go maps have a non-deterministic order when iterating the keys, so @@ -686,10 +805,27 @@ func (c *tomlConfig) loadConfig(path string, forceV2 bool) error { } sort.Strings(prefixes) - c.Registries = []Registry{} + c.partialV2.Registries = []Registry{} for _, prefix := range prefixes { - c.Registries = append(c.Registries, registryMap[prefix]) + c.partialV2.Registries = append(c.partialV2.Registries, registryMap[prefix]) } - return nil + // == Merge UnqualifiedSearchRegistries: + // This depends on an subtlety of the behavior of the TOML decoder, where a missing array field + // is not modified while unmarshaling (in our case remains to nil), while an [] is unmarshaled + // as a non-nil []string{}. + if updates.partialV2.UnqualifiedSearchRegistries != nil { + c.partialV2.UnqualifiedSearchRegistries = updates.partialV2.UnqualifiedSearchRegistries + c.unqualifiedSearchRegistriesOrigin = updates.unqualifiedSearchRegistriesOrigin + } + + // == Merge shortNameMode: + // We don’t maintain c.partialV2.ShortNameMode. + if updates.shortNameMode != types.ShortNameModeInvalid { + c.shortNameMode = updates.shortNameMode + } + + // == Merge aliasCache: + // We don’t maintain (in fact we actively clear) c.partialV2.shortNameAliasConf. + c.aliasCache.updateWithConfigurationFrom(updates.aliasCache) } diff --git a/vendor/github.com/containers/image/v5/types/types.go b/vendor/github.com/containers/image/v5/types/types.go index 5a91f0096..3c5126b4e 100644 --- a/vendor/github.com/containers/image/v5/types/types.go +++ b/vendor/github.com/containers/image/v5/types/types.go @@ -486,6 +486,36 @@ func NewOptionalBool(b bool) OptionalBool { return o } +// ShortNameMode defines the mode of short-name resolution. +// +// The use of unqualified-search registries entails an ambiguity as it's +// unclear from which registry a given image, referenced by a short name, may +// be pulled from. +// +// The ShortNameMode type defines how short names should resolve. +type ShortNameMode int + +const ( + ShortNameModeInvalid ShortNameMode = iota + // Use all configured unqualified-search registries without prompting + // the user. + ShortNameModeDisabled + // If stdout and stdin are a TTY, prompt the user to select a configured + // unqualified-search registry. Otherwise, use all configured + // unqualified-search registries. + // + // Note that if only one unqualified-search registry is set, it will be + // used without prompting. + ShortNameModePermissive + // Always prompt the user to select a configured unqualified-search + // registry. Throw an error if stdout or stdin is not a TTY as + // prompting isn't possible. + // + // Note that if only one unqualified-search registry is set, it will be + // used without prompting. + ShortNameModeEnforcing +) + // SystemContext allows parameterizing access to implicitly-accessed resources, // like configuration files in /etc and users' login state in their home directory. // Various components can share the same field only if their semantics is exactly @@ -509,6 +539,10 @@ type SystemContext struct { SystemRegistriesConfPath string // Path to the system-wide registries configuration directory SystemRegistriesConfDirPath string + // Path to the user-specific short-names configuration file + UserShortNameAliasConfPath string + // If set, short-name resolution in pkg/shortnames must follow the specified mode + ShortNameMode *ShortNameMode // If not "", overrides the default path for the authentication file, but only new format files AuthFilePath string // if not "", overrides the default path for the authentication file, but with the legacy format; diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index b6b79f26c..3ef1c2410 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -6,7 +6,7 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 5 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 7 + VersionMinor = 8 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 diff --git a/vendor/github.com/juju/ansiterm/LICENSE b/vendor/github.com/juju/ansiterm/LICENSE new file mode 100644 index 000000000..ade9307b3 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/LICENSE @@ -0,0 +1,191 @@ +All files in this repository are licensed as follows. If you contribute +to this repository, it is assumed that you license your contribution +under the same license unless you state otherwise. + +All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. + +This software is licensed under the LGPLv3, included below. + +As a special exception to the GNU Lesser General Public License version 3 +("LGPL3"), the copyright holders of this Library give you permission to +convey to a third party a Combined Work that links statically or dynamically +to this Library without providing any Minimal Corresponding Source or +Minimal Application Code as set out in 4d or providing the installation +information set out in section 4e, provided that you comply with the other +provisions of LGPL3 and provided that you meet, for the Application the +terms and conditions of the license(s) which apply to the Application. + +Except as stated in this special exception, the provisions of LGPL3 will +continue to comply in full to this Library. If you modify this Library, you +may apply this exception to your version of this Library, but you are not +obliged to do so. If you do not wish to do so, delete this exception +statement from your version. This exception does not (and cannot) modify any +license terms which apply to the Application, with which you must still +comply. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/github.com/juju/ansiterm/Makefile b/vendor/github.com/juju/ansiterm/Makefile new file mode 100644 index 000000000..212fdcbe5 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/Makefile @@ -0,0 +1,14 @@ +# Copyright 2016 Canonical Ltd. +# Licensed under the LGPLv3, see LICENCE file for details. + +default: check + +check: + go test + +docs: + godoc2md github.com/juju/ansiterm > README.md + sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/ansiterm?status.svg)](https://godoc.org/github.com/juju/ansiterm)|' README.md + + +.PHONY: default check docs diff --git a/vendor/github.com/juju/ansiterm/README.md b/vendor/github.com/juju/ansiterm/README.md new file mode 100644 index 000000000..567438721 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/README.md @@ -0,0 +1,323 @@ + +# ansiterm + import "github.com/juju/ansiterm" + +Package ansiterm provides a Writer that writes out the ANSI escape +codes for color and styles. + + + + + + + +## type Color +``` go +type Color int +``` +Color represents one of the standard 16 ANSI colors. + + + +``` go +const ( + Default Color + Black + Red + Green + Yellow + Blue + Magenta + Cyan + Gray + DarkGray + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + White +) +``` + + + + + + + + +### func (Color) String +``` go +func (c Color) String() string +``` +String returns the name of the color. + + + +## type Context +``` go +type Context struct { + Foreground Color + Background Color + Styles []Style +} +``` +Context provides a way to specify both foreground and background colors +along with other styles and write text to a Writer with those colors and +styles. + + + + + + + + + +### func Background +``` go +func Background(color Color) *Context +``` +Background is a convenience function that creates a Context with the +specified color as the background color. + + +### func Foreground +``` go +func Foreground(color Color) *Context +``` +Foreground is a convenience function that creates a Context with the +specified color as the foreground color. + + +### func Styles +``` go +func Styles(styles ...Style) *Context +``` +Styles is a convenience function that creates a Context with the +specified styles set. + + + + +### func (\*Context) Fprint +``` go +func (c *Context) Fprint(w sgrWriter, args ...interface{}) +``` +Fprint will set the sgr values of the writer to the specified foreground, +background and styles, then formats using the default formats for its +operands and writes to w. Spaces are added between operands when neither is +a string. It returns the number of bytes written and any write error +encountered. + + + +### func (\*Context) Fprintf +``` go +func (c *Context) Fprintf(w sgrWriter, format string, args ...interface{}) +``` +Fprintf will set the sgr values of the writer to the specified +foreground, background and styles, then write the formatted string, +then reset the writer. + + + +### func (\*Context) SetBackground +``` go +func (c *Context) SetBackground(color Color) *Context +``` +SetBackground sets the background to the specified color. + + + +### func (\*Context) SetForeground +``` go +func (c *Context) SetForeground(color Color) *Context +``` +SetForeground sets the foreground to the specified color. + + + +### func (\*Context) SetStyle +``` go +func (c *Context) SetStyle(styles ...Style) *Context +``` +SetStyle replaces the styles with the new values. + + + +## type Style +``` go +type Style int +``` + + +``` go +const ( + Bold Style + Faint + Italic + Underline + Blink + Reverse + Strikethrough + Conceal +) +``` + + + + + + + + +### func (Style) String +``` go +func (s Style) String() string +``` + + +## type TabWriter +``` go +type TabWriter struct { + Writer + // contains filtered or unexported fields +} +``` +TabWriter is a filter that inserts padding around tab-delimited +columns in its input to align them in the output. + +It also setting of colors and styles over and above the standard +tabwriter package. + + + + + + + + + +### func NewTabWriter +``` go +func NewTabWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter +``` +NewTabWriter returns a writer that is able to set colors and styels. +The ansi escape codes are stripped for width calculations. + + + + +### func (\*TabWriter) Flush +``` go +func (t *TabWriter) Flush() error +``` +Flush should be called after the last call to Write to ensure +that any data buffered in the Writer is written to output. Any +incomplete escape sequence at the end is considered +complete for formatting purposes. + + + +### func (\*TabWriter) Init +``` go +func (t *TabWriter) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter +``` +A Writer must be initialized with a call to Init. The first parameter (output) +specifies the filter output. The remaining parameters control the formatting: + + + minwidth minimal cell width including any padding + tabwidth width of tab characters (equivalent number of spaces) + padding padding added to a cell before computing its width + padchar ASCII char used for padding + if padchar == '\t', the Writer will assume that the + width of a '\t' in the formatted output is tabwidth, + and cells are left-aligned independent of align_left + (for correct-looking results, tabwidth must correspond + to the tab width in the viewer displaying the result) + flags formatting control + + + +## type Writer +``` go +type Writer struct { + io.Writer + // contains filtered or unexported fields +} +``` +Writer allows colors and styles to be specified. If the io.Writer +is not a terminal capable of color, all attempts to set colors or +styles are no-ops. + + + + + + + + + +### func NewWriter +``` go +func NewWriter(w io.Writer) *Writer +``` +NewWriter returns a Writer that allows the caller to specify colors and +styles. If the io.Writer is not a terminal capable of color, all attempts +to set colors or styles are no-ops. + + + + +### func (\*Writer) ClearStyle +``` go +func (w *Writer) ClearStyle(s Style) +``` +ClearStyle clears the text style. + + + +### func (\*Writer) Reset +``` go +func (w *Writer) Reset() +``` +Reset returns the default foreground and background colors with no styles. + + + +### func (\*Writer) SetBackground +``` go +func (w *Writer) SetBackground(c Color) +``` +SetBackground sets the background color. + + + +### func (\*Writer) SetForeground +``` go +func (w *Writer) SetForeground(c Color) +``` +SetForeground sets the foreground color. + + + +### func (\*Writer) SetStyle +``` go +func (w *Writer) SetStyle(s Style) +``` +SetStyle sets the text style. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
\ No newline at end of file diff --git a/vendor/github.com/juju/ansiterm/attribute.go b/vendor/github.com/juju/ansiterm/attribute.go new file mode 100644 index 000000000..f2daa4813 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/attribute.go @@ -0,0 +1,50 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "sort" + "strings" +) + +type attribute int + +const ( + unknownAttribute attribute = -1 + reset attribute = 0 +) + +// sgr returns the escape sequence for the Select Graphic Rendition +// for the attribute. +func (a attribute) sgr() string { + if a < 0 { + return "" + } + return fmt.Sprintf("\x1b[%dm", a) +} + +type attributes []attribute + +func (a attributes) Len() int { return len(a) } +func (a attributes) Less(i, j int) bool { return a[i] < a[j] } +func (a attributes) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// sgr returns the combined escape sequence for the Select Graphic Rendition +// for the sequence of attributes. +func (a attributes) sgr() string { + switch len(a) { + case 0: + return "" + case 1: + return a[0].sgr() + default: + sort.Sort(a) + var values []string + for _, attr := range a { + values = append(values, fmt.Sprint(attr)) + } + return fmt.Sprintf("\x1b[%sm", strings.Join(values, ";")) + } +} diff --git a/vendor/github.com/juju/ansiterm/color.go b/vendor/github.com/juju/ansiterm/color.go new file mode 100644 index 000000000..0a97de31e --- /dev/null +++ b/vendor/github.com/juju/ansiterm/color.go @@ -0,0 +1,119 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +const ( + _ Color = iota + Default + Black + Red + Green + Yellow + Blue + Magenta + Cyan + Gray + DarkGray + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + White +) + +// Color represents one of the standard 16 ANSI colors. +type Color int + +// String returns the name of the color. +func (c Color) String() string { + switch c { + case Default: + return "default" + case Black: + return "black" + case Red: + return "red" + case Green: + return "green" + case Yellow: + return "yellow" + case Blue: + return "blue" + case Magenta: + return "magenta" + case Cyan: + return "cyan" + case Gray: + return "gray" + case DarkGray: + return "darkgray" + case BrightRed: + return "brightred" + case BrightGreen: + return "brightgreen" + case BrightYellow: + return "brightyellow" + case BrightBlue: + return "brightblue" + case BrightMagenta: + return "brightmagenta" + case BrightCyan: + return "brightcyan" + case White: + return "white" + default: + return "" + } +} + +func (c Color) foreground() attribute { + switch c { + case Default: + return 39 + case Black: + return 30 + case Red: + return 31 + case Green: + return 32 + case Yellow: + return 33 + case Blue: + return 34 + case Magenta: + return 35 + case Cyan: + return 36 + case Gray: + return 37 + case DarkGray: + return 90 + case BrightRed: + return 91 + case BrightGreen: + return 92 + case BrightYellow: + return 93 + case BrightBlue: + return 94 + case BrightMagenta: + return 95 + case BrightCyan: + return 96 + case White: + return 97 + default: + return unknownAttribute + } +} + +func (c Color) background() attribute { + value := c.foreground() + if value != unknownAttribute { + return value + 10 + } + return value +} diff --git a/vendor/github.com/juju/ansiterm/context.go b/vendor/github.com/juju/ansiterm/context.go new file mode 100644 index 000000000..e61a867ff --- /dev/null +++ b/vendor/github.com/juju/ansiterm/context.go @@ -0,0 +1,95 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "io" +) + +// Context provides a way to specify both foreground and background colors +// along with other styles and write text to a Writer with those colors and +// styles. +type Context struct { + Foreground Color + Background Color + Styles []Style +} + +// Foreground is a convenience function that creates a Context with the +// specified color as the foreground color. +func Foreground(color Color) *Context { + return &Context{Foreground: color} +} + +// Background is a convenience function that creates a Context with the +// specified color as the background color. +func Background(color Color) *Context { + return &Context{Background: color} +} + +// Styles is a convenience function that creates a Context with the +// specified styles set. +func Styles(styles ...Style) *Context { + return &Context{Styles: styles} +} + +// SetForeground sets the foreground to the specified color. +func (c *Context) SetForeground(color Color) *Context { + c.Foreground = color + return c +} + +// SetBackground sets the background to the specified color. +func (c *Context) SetBackground(color Color) *Context { + c.Background = color + return c +} + +// SetStyle replaces the styles with the new values. +func (c *Context) SetStyle(styles ...Style) *Context { + c.Styles = styles + return c +} + +type sgrWriter interface { + io.Writer + writeSGR(value sgr) +} + +// Fprintf will set the sgr values of the writer to the specified +// foreground, background and styles, then write the formatted string, +// then reset the writer. +func (c *Context) Fprintf(w sgrWriter, format string, args ...interface{}) { + w.writeSGR(c) + fmt.Fprintf(w, format, args...) + w.writeSGR(reset) +} + +// Fprint will set the sgr values of the writer to the specified foreground, +// background and styles, then formats using the default formats for its +// operands and writes to w. Spaces are added between operands when neither is +// a string. It returns the number of bytes written and any write error +// encountered. +func (c *Context) Fprint(w sgrWriter, args ...interface{}) { + w.writeSGR(c) + fmt.Fprint(w, args...) + w.writeSGR(reset) +} + +func (c *Context) sgr() string { + var values attributes + if foreground := c.Foreground.foreground(); foreground != unknownAttribute { + values = append(values, foreground) + } + if background := c.Background.background(); background != unknownAttribute { + values = append(values, background) + } + for _, style := range c.Styles { + if value := style.enable(); value != unknownAttribute { + values = append(values, value) + } + } + return values.sgr() +} diff --git a/vendor/github.com/juju/ansiterm/doc.go b/vendor/github.com/juju/ansiterm/doc.go new file mode 100644 index 000000000..782700779 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/doc.go @@ -0,0 +1,6 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +// Package ansiterm provides a Writer that writes out the ANSI escape +// codes for color and styles. +package ansiterm diff --git a/vendor/github.com/juju/ansiterm/style.go b/vendor/github.com/juju/ansiterm/style.go new file mode 100644 index 000000000..0be42da56 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/style.go @@ -0,0 +1,72 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +const ( + _ Style = iota + Bold + Faint + Italic + Underline + Blink + Reverse + Strikethrough + Conceal +) + +type Style int + +func (s Style) String() string { + switch s { + case Bold: + return "bold" + case Faint: + return "faint" + case Italic: + return "italic" + case Underline: + return "underline" + case Blink: + return "blink" + case Reverse: + return "reverse" + case Strikethrough: + return "strikethrough" + case Conceal: + return "conceal" + default: + return "" + } +} + +func (s Style) enable() attribute { + switch s { + case Bold: + return 1 + case Faint: + return 2 + case Italic: + return 3 + case Underline: + return 4 + case Blink: + return 5 + case Reverse: + return 7 + case Conceal: + return 8 + case Strikethrough: + return 9 + default: + return unknownAttribute + } +} + +func (s Style) disable() attribute { + value := s.enable() + if value != unknownAttribute { + return value + 20 + } + return value +} diff --git a/vendor/github.com/juju/ansiterm/tabwriter.go b/vendor/github.com/juju/ansiterm/tabwriter.go new file mode 100644 index 000000000..1ff6faaaf --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter.go @@ -0,0 +1,64 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "io" + + "github.com/juju/ansiterm/tabwriter" +) + +// NewTabWriter returns a writer that is able to set colors and styels. +// The ansi escape codes are stripped for width calculations. +func NewTabWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { + return new(TabWriter).Init(output, minwidth, tabwidth, padding, padchar, flags) +} + +// TabWriter is a filter that inserts padding around tab-delimited +// columns in its input to align them in the output. +// +// It also setting of colors and styles over and above the standard +// tabwriter package. +type TabWriter struct { + Writer + tw tabwriter.Writer +} + +// Flush should be called after the last call to Write to ensure +// that any data buffered in the Writer is written to output. Any +// incomplete escape sequence at the end is considered +// complete for formatting purposes. +// +func (t *TabWriter) Flush() error { + return t.tw.Flush() +} + +// SetColumnAlignRight will mark a particular column as align right. +// This is reset on the next flush. +func (t *TabWriter) SetColumnAlignRight(column int) { + t.tw.SetColumnAlignRight(column) +} + +// A Writer must be initialized with a call to Init. The first parameter (output) +// specifies the filter output. The remaining parameters control the formatting: +// +// minwidth minimal cell width including any padding +// tabwidth width of tab characters (equivalent number of spaces) +// padding padding added to a cell before computing its width +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwidth, +// and cells are left-aligned independent of align_left +// (for correct-looking results, tabwidth must correspond +// to the tab width in the viewer displaying the result) +// flags formatting control +// +func (t *TabWriter) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { + writer, colorCapable := colorEnabledWriter(output) + t.Writer = Writer{ + Writer: t.tw.Init(writer, minwidth, tabwidth, padding, padchar, flags), + noColor: !colorCapable, + } + return t +} diff --git a/vendor/github.com/juju/ansiterm/tabwriter/LICENSE b/vendor/github.com/juju/ansiterm/tabwriter/LICENSE new file mode 100644 index 000000000..744875676 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go b/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go new file mode 100644 index 000000000..98949d036 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go @@ -0,0 +1,587 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is mostly a copy of the go standard library text/tabwriter. With +// the additional stripping of ansi control characters for width calculations. + +// Package tabwriter implements a write filter (tabwriter.Writer) that +// translates tabbed columns in input into properly aligned text. +// +// The package is using the Elastic Tabstops algorithm described at +// http://nickgravgaard.com/elastictabstops/index.html. +// +package tabwriter + +import ( + "bytes" + "io" + "unicode/utf8" + + "github.com/lunixbochs/vtclean" +) + +// ---------------------------------------------------------------------------- +// Filter implementation + +// A cell represents a segment of text terminated by tabs or line breaks. +// The text itself is stored in a separate buffer; cell only describes the +// segment's size in bytes, its width in runes, and whether it's an htab +// ('\t') terminated cell. +// +type cell struct { + size int // cell size in bytes + width int // cell width in runes + htab bool // true if the cell is terminated by an htab ('\t') +} + +// A Writer is a filter that inserts padding around tab-delimited +// columns in its input to align them in the output. +// +// The Writer treats incoming bytes as UTF-8 encoded text consisting +// of cells terminated by (horizontal or vertical) tabs or line +// breaks (newline or formfeed characters). Cells in adjacent lines +// constitute a column. The Writer inserts padding as needed to +// make all cells in a column have the same width, effectively +// aligning the columns. It assumes that all characters have the +// same width except for tabs for which a tabwidth must be specified. +// Note that cells are tab-terminated, not tab-separated: trailing +// non-tab text at the end of a line does not form a column cell. +// +// The Writer assumes that all Unicode code points have the same width; +// this may not be true in some fonts. +// +// If DiscardEmptyColumns is set, empty columns that are terminated +// entirely by vertical (or "soft") tabs are discarded. Columns +// terminated by horizontal (or "hard") tabs are not affected by +// this flag. +// +// If a Writer is configured to filter HTML, HTML tags and entities +// are passed through. The widths of tags and entities are +// assumed to be zero (tags) and one (entities) for formatting purposes. +// +// A segment of text may be escaped by bracketing it with Escape +// characters. The tabwriter passes escaped text segments through +// unchanged. In particular, it does not interpret any tabs or line +// breaks within the segment. If the StripEscape flag is set, the +// Escape characters are stripped from the output; otherwise they +// are passed through as well. For the purpose of formatting, the +// width of the escaped text is always computed excluding the Escape +// characters. +// +// The formfeed character ('\f') acts like a newline but it also +// terminates all columns in the current line (effectively calling +// Flush). Cells in the next line start new columns. Unless found +// inside an HTML tag or inside an escaped text segment, formfeed +// characters appear as newlines in the output. +// +// The Writer must buffer input internally, because proper spacing +// of one line may depend on the cells in future lines. Clients must +// call Flush when done calling Write. +// +type Writer struct { + // configuration + output io.Writer + minwidth int + tabwidth int + padding int + padbytes [8]byte + flags uint + + // current state + buf bytes.Buffer // collected text excluding tabs or line breaks + pos int // buffer position up to which cell.width of incomplete cell has been computed + cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections + endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0) + lines [][]cell // list of lines; each line is a list of cells + widths []int // list of column widths in runes - re-used during formatting + alignment map[int]uint // column alignment +} + +func (b *Writer) addLine() { b.lines = append(b.lines, []cell{}) } + +// Reset the current state. +func (b *Writer) reset() { + b.buf.Reset() + b.pos = 0 + b.cell = cell{} + b.endChar = 0 + b.lines = b.lines[0:0] + b.widths = b.widths[0:0] + b.alignment = make(map[int]uint) + b.addLine() +} + +// Internal representation (current state): +// +// - all text written is appended to buf; tabs and line breaks are stripped away +// - at any given time there is a (possibly empty) incomplete cell at the end +// (the cell starts after a tab or line break) +// - cell.size is the number of bytes belonging to the cell so far +// - cell.width is text width in runes of that cell from the start of the cell to +// position pos; html tags and entities are excluded from this width if html +// filtering is enabled +// - the sizes and widths of processed text are kept in the lines list +// which contains a list of cells for each line +// - the widths list is a temporary list with current widths used during +// formatting; it is kept in Writer because it's re-used +// +// |<---------- size ---------->| +// | | +// |<- width ->|<- ignored ->| | +// | | | | +// [---processed---tab------------<tag>...</tag>...] +// ^ ^ ^ +// | | | +// buf start of incomplete cell pos + +// Formatting can be controlled with these flags. +const ( + // Ignore html tags and treat entities (starting with '&' + // and ending in ';') as single characters (width = 1). + FilterHTML uint = 1 << iota + + // Strip Escape characters bracketing escaped text segments + // instead of passing them through unchanged with the text. + StripEscape + + // Force right-alignment of cell content. + // Default is left-alignment. + AlignRight + + // Handle empty columns as if they were not present in + // the input in the first place. + DiscardEmptyColumns + + // Always use tabs for indentation columns (i.e., padding of + // leading empty cells on the left) independent of padchar. + TabIndent + + // Print a vertical bar ('|') between columns (after formatting). + // Discarded columns appear as zero-width columns ("||"). + Debug +) + +// A Writer must be initialized with a call to Init. The first parameter (output) +// specifies the filter output. The remaining parameters control the formatting: +// +// minwidth minimal cell width including any padding +// tabwidth width of tab characters (equivalent number of spaces) +// padding padding added to a cell before computing its width +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwidth, +// and cells are left-aligned independent of align_left +// (for correct-looking results, tabwidth must correspond +// to the tab width in the viewer displaying the result) +// flags formatting control +// +func (b *Writer) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer { + if minwidth < 0 || tabwidth < 0 || padding < 0 { + panic("negative minwidth, tabwidth, or padding") + } + b.output = output + b.minwidth = minwidth + b.tabwidth = tabwidth + b.padding = padding + for i := range b.padbytes { + b.padbytes[i] = padchar + } + if padchar == '\t' { + // tab padding enforces left-alignment + flags &^= AlignRight + } + b.flags = flags + + b.reset() + + return b +} + +// debugging support (keep code around) +func (b *Writer) dump() { + pos := 0 + for i, line := range b.lines { + print("(", i, ") ") + for _, c := range line { + print("[", string(b.buf.Bytes()[pos:pos+c.size]), "]") + pos += c.size + } + print("\n") + } + print("\n") +} + +// local error wrapper so we can distinguish errors we want to return +// as errors from genuine panics (which we don't want to return as errors) +type osError struct { + err error +} + +func (b *Writer) write0(buf []byte) { + n, err := b.output.Write(buf) + if n != len(buf) && err == nil { + err = io.ErrShortWrite + } + if err != nil { + panic(osError{err}) + } +} + +func (b *Writer) writeN(src []byte, n int) { + for n > len(src) { + b.write0(src) + n -= len(src) + } + b.write0(src[0:n]) +} + +var ( + newline = []byte{'\n'} + tabs = []byte("\t\t\t\t\t\t\t\t") +) + +func (b *Writer) writePadding(textw, cellw int, useTabs bool) { + if b.padbytes[0] == '\t' || useTabs { + // padding is done with tabs + if b.tabwidth == 0 { + return // tabs have no width - can't do any padding + } + // make cellw the smallest multiple of b.tabwidth + cellw = (cellw + b.tabwidth - 1) / b.tabwidth * b.tabwidth + n := cellw - textw // amount of padding + if n < 0 { + panic("internal error") + } + b.writeN(tabs, (n+b.tabwidth-1)/b.tabwidth) + return + } + + // padding is done with non-tab characters + b.writeN(b.padbytes[0:], cellw-textw) +} + +var vbar = []byte{'|'} + +func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) { + pos = pos0 + for i := line0; i < line1; i++ { + line := b.lines[i] + + // if TabIndent is set, use tabs to pad leading empty cells + useTabs := b.flags&TabIndent != 0 + + for j, c := range line { + if j > 0 && b.flags&Debug != 0 { + // indicate column break + b.write0(vbar) + } + + if c.size == 0 { + // empty cell + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], useTabs) + } + } else { + // non-empty cell + useTabs = false + alignColumnRight := b.alignment[j] == AlignRight + if (b.flags&AlignRight == 0) && !alignColumnRight { // align left + b.write0(b.buf.Bytes()[pos : pos+c.size]) + pos += c.size + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], false) + } + } else if alignColumnRight && j < len(b.widths) { + // just this column + internalSize := b.widths[j] - b.padding + if j < len(b.widths) { + b.writePadding(c.width, internalSize, false) + } + b.write0(b.buf.Bytes()[pos : pos+c.size]) + if b.padding > 0 { + b.writePadding(0, b.padding, false) + } + pos += c.size + } else { // align right + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], false) + } + b.write0(b.buf.Bytes()[pos : pos+c.size]) + pos += c.size + } + } + } + + if i+1 == len(b.lines) { + // last buffered line - we don't have a newline, so just write + // any outstanding buffered data + b.write0(b.buf.Bytes()[pos : pos+b.cell.size]) + pos += b.cell.size + } else { + // not the last line - write newline + b.write0(newline) + } + } + return +} + +// Format the text between line0 and line1 (excluding line1); pos +// is the buffer position corresponding to the beginning of line0. +// Returns the buffer position corresponding to the beginning of +// line1 and an error, if any. +// +func (b *Writer) format(pos0 int, line0, line1 int) (pos int) { + pos = pos0 + column := len(b.widths) + for this := line0; this < line1; this++ { + line := b.lines[this] + + if column < len(line)-1 { + // cell exists in this column => this line + // has more cells than the previous line + // (the last cell per line is ignored because cells are + // tab-terminated; the last cell per line describes the + // text before the newline/formfeed and does not belong + // to a column) + + // print unprinted lines until beginning of block + pos = b.writeLines(pos, line0, this) + line0 = this + + // column block begin + width := b.minwidth // minimal column width + discardable := true // true if all cells in this column are empty and "soft" + for ; this < line1; this++ { + line = b.lines[this] + if column < len(line)-1 { + // cell exists in this column + c := line[column] + // update width + if w := c.width + b.padding; w > width { + width = w + } + // update discardable + if c.width > 0 || c.htab { + discardable = false + } + } else { + break + } + } + // column block end + + // discard empty columns if necessary + if discardable && b.flags&DiscardEmptyColumns != 0 { + width = 0 + } + + // format and print all columns to the right of this column + // (we know the widths of this column and all columns to the left) + b.widths = append(b.widths, width) // push width + pos = b.format(pos, line0, this) + b.widths = b.widths[0 : len(b.widths)-1] // pop width + line0 = this + } + } + + // print unprinted lines until end + return b.writeLines(pos, line0, line1) +} + +// Append text to current cell. +func (b *Writer) append(text []byte) { + b.buf.Write(text) + b.cell.size += len(text) +} + +// Update the cell width. +func (b *Writer) updateWidth() { + // ---- Changes here ----- + newChars := b.buf.Bytes()[b.pos:b.buf.Len()] + cleaned := vtclean.Clean(string(newChars), false) // false to strip colors + b.cell.width += utf8.RuneCount([]byte(cleaned)) + // --- end of changes ---- + b.pos = b.buf.Len() +} + +// To escape a text segment, bracket it with Escape characters. +// For instance, the tab in this string "Ignore this tab: \xff\t\xff" +// does not terminate a cell and constitutes a single character of +// width one for formatting purposes. +// +// The value 0xff was chosen because it cannot appear in a valid UTF-8 sequence. +// +const Escape = '\xff' + +// Start escaped mode. +func (b *Writer) startEscape(ch byte) { + switch ch { + case Escape: + b.endChar = Escape + case '<': + b.endChar = '>' + case '&': + b.endChar = ';' + } +} + +// Terminate escaped mode. If the escaped text was an HTML tag, its width +// is assumed to be zero for formatting purposes; if it was an HTML entity, +// its width is assumed to be one. In all other cases, the width is the +// unicode width of the text. +// +func (b *Writer) endEscape() { + switch b.endChar { + case Escape: + b.updateWidth() + if b.flags&StripEscape == 0 { + b.cell.width -= 2 // don't count the Escape chars + } + case '>': // tag of zero width + case ';': + b.cell.width++ // entity, count as one rune + } + b.pos = b.buf.Len() + b.endChar = 0 +} + +// Terminate the current cell by adding it to the list of cells of the +// current line. Returns the number of cells in that line. +// +func (b *Writer) terminateCell(htab bool) int { + b.cell.htab = htab + line := &b.lines[len(b.lines)-1] + *line = append(*line, b.cell) + b.cell = cell{} + return len(*line) +} + +func handlePanic(err *error, op string) { + if e := recover(); e != nil { + if nerr, ok := e.(osError); ok { + *err = nerr.err + return + } + panic("tabwriter: panic during " + op) + } +} + +// Flush should be called after the last call to Write to ensure +// that any data buffered in the Writer is written to output. Any +// incomplete escape sequence at the end is considered +// complete for formatting purposes. +// +func (b *Writer) Flush() (err error) { + defer b.reset() // even in the presence of errors + defer handlePanic(&err, "Flush") + + // add current cell if not empty + if b.cell.size > 0 { + if b.endChar != 0 { + // inside escape - terminate it even if incomplete + b.endEscape() + } + b.terminateCell(false) + } + + // format contents of buffer + b.format(0, 0, len(b.lines)) + + return +} + +var hbar = []byte("---\n") + +// SetColumnAlignRight will mark a particular column as align right. +// This is reset on the next flush. +func (b *Writer) SetColumnAlignRight(column int) { + b.alignment[column] = AlignRight +} + +// Write writes buf to the writer b. +// The only errors returned are ones encountered +// while writing to the underlying output stream. +// +func (b *Writer) Write(buf []byte) (n int, err error) { + defer handlePanic(&err, "Write") + + // split text into cells + n = 0 + for i, ch := range buf { + if b.endChar == 0 { + // outside escape + switch ch { + case '\t', '\v', '\n', '\f': + // end of cell + b.append(buf[n:i]) + b.updateWidth() + n = i + 1 // ch consumed + ncells := b.terminateCell(ch == '\t') + if ch == '\n' || ch == '\f' { + // terminate line + b.addLine() + if ch == '\f' || ncells == 1 { + // A '\f' always forces a flush. Otherwise, if the previous + // line has only one cell which does not have an impact on + // the formatting of the following lines (the last cell per + // line is ignored by format()), thus we can flush the + // Writer contents. + if err = b.Flush(); err != nil { + return + } + if ch == '\f' && b.flags&Debug != 0 { + // indicate section break + b.write0(hbar) + } + } + } + + case Escape: + // start of escaped sequence + b.append(buf[n:i]) + b.updateWidth() + n = i + if b.flags&StripEscape != 0 { + n++ // strip Escape + } + b.startEscape(Escape) + + case '<', '&': + // possibly an html tag/entity + if b.flags&FilterHTML != 0 { + // begin of tag/entity + b.append(buf[n:i]) + b.updateWidth() + n = i + b.startEscape(ch) + } + } + + } else { + // inside escape + if ch == b.endChar { + // end of tag/entity + j := i + 1 + if ch == Escape && b.flags&StripEscape != 0 { + j = i // strip Escape + } + b.append(buf[n:j]) + n = i + 1 // ch consumed + b.endEscape() + } + } + } + + // append leftover text + b.append(buf[n:]) + n = len(buf) + return +} + +// NewWriter allocates and initializes a new tabwriter.Writer. +// The parameters are the same as for the Init function. +// +func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer { + return new(Writer).Init(output, minwidth, tabwidth, padding, padchar, flags) +} diff --git a/vendor/github.com/juju/ansiterm/terminal.go b/vendor/github.com/juju/ansiterm/terminal.go new file mode 100644 index 000000000..96fd11c51 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/terminal.go @@ -0,0 +1,32 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "io" + "os" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// colorEnabledWriter returns a writer that can handle the ansi color codes +// and true if the writer passed in is a terminal capable of color. If the +// TERM environment variable is set to "dumb", the terminal is not considered +// color capable. +func colorEnabledWriter(w io.Writer) (io.Writer, bool) { + f, ok := w.(*os.File) + if !ok { + return w, false + } + // Check the TERM environment variable specifically + // to check for "dumb" terminals. + if os.Getenv("TERM") == "dumb" { + return w, false + } + if !isatty.IsTerminal(f.Fd()) { + return w, false + } + return colorable.NewColorable(f), true +} diff --git a/vendor/github.com/juju/ansiterm/writer.go b/vendor/github.com/juju/ansiterm/writer.go new file mode 100644 index 000000000..32437bb27 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/writer.go @@ -0,0 +1,74 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "io" +) + +// Writer allows colors and styles to be specified. If the io.Writer +// is not a terminal capable of color, all attempts to set colors or +// styles are no-ops. +type Writer struct { + io.Writer + + noColor bool +} + +// NewWriter returns a Writer that allows the caller to specify colors and +// styles. If the io.Writer is not a terminal capable of color, all attempts +// to set colors or styles are no-ops. +func NewWriter(w io.Writer) *Writer { + writer, colorCapable := colorEnabledWriter(w) + return &Writer{ + Writer: writer, + noColor: !colorCapable, + } +} + +// SetColorCapable forces the writer to either write the ANSI escape color +// if capable is true, or to not write them if capable is false. +func (w *Writer) SetColorCapable(capable bool) { + w.noColor = !capable +} + +// SetForeground sets the foreground color. +func (w *Writer) SetForeground(c Color) { + w.writeSGR(c.foreground()) +} + +// SetBackground sets the background color. +func (w *Writer) SetBackground(c Color) { + w.writeSGR(c.background()) +} + +// SetStyle sets the text style. +func (w *Writer) SetStyle(s Style) { + w.writeSGR(s.enable()) +} + +// ClearStyle clears the text style. +func (w *Writer) ClearStyle(s Style) { + w.writeSGR(s.disable()) +} + +// Reset returns the default foreground and background colors with no styles. +func (w *Writer) Reset() { + w.writeSGR(reset) +} + +type sgr interface { + // sgr returns the combined escape sequence for the Select Graphic Rendition. + sgr() string +} + +// writeSGR takes the appropriate integer SGR parameters +// and writes out the ANIS escape code. +func (w *Writer) writeSGR(value sgr) { + if w.noColor { + return + } + fmt.Fprint(w, value.sgr()) +} diff --git a/vendor/github.com/lunixbochs/vtclean/.travis.yml b/vendor/github.com/lunixbochs/vtclean/.travis.yml new file mode 100644 index 000000000..fc0a54325 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/.travis.yml @@ -0,0 +1,9 @@ +language: go +sudo: false + +script: go test -v + +go: + - 1.5 + - 1.6 + - 1.7 diff --git a/vendor/github.com/lunixbochs/vtclean/LICENSE b/vendor/github.com/lunixbochs/vtclean/LICENSE new file mode 100644 index 000000000..42e82633f --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Ryan Hileman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/lunixbochs/vtclean/README.md b/vendor/github.com/lunixbochs/vtclean/README.md new file mode 100644 index 000000000..99910a460 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/README.md @@ -0,0 +1,46 @@ +[![Build Status](https://travis-ci.org/lunixbochs/vtclean.svg?branch=master)](https://travis-ci.org/lunixbochs/vtclean) + +vtclean +---- + +Clean up raw terminal output by stripping escape sequences, optionally preserving color. + +Get it: `go get github.com/lunixbochs/vtclean/vtclean` + +API: + + import "github.com/lunixbochs/vtclean" + vtclean.Clean(line string, color bool) string + +Command line example: + + $ echo -e '\x1b[1;32mcolor example + color forced to stop at end of line + backspace is ba\b\bgood + no beeps!\x07\x07' | ./vtclean -color + + color example + color forced to stop at end of line + backspace is good + no beeps! + +Go example: + + package main + + import ( + "fmt" + "github.com/lunixbochs/vtclean" + ) + + func main() { + line := vtclean.Clean( + "\033[1;32mcolor, " + + "curs\033[Aor, " + + "backspace\b\b\b\b\b\b\b\b\b\b\b\033[K", false) + fmt.Println(line) + } + +Output: + + color, cursor diff --git a/vendor/github.com/lunixbochs/vtclean/io.go b/vendor/github.com/lunixbochs/vtclean/io.go new file mode 100644 index 000000000..31be0076a --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/io.go @@ -0,0 +1,93 @@ +package vtclean + +import ( + "bufio" + "bytes" + "io" +) + +type reader struct { + io.Reader + scanner *bufio.Scanner + buf []byte + + color bool +} + +func NewReader(r io.Reader, color bool) io.Reader { + return &reader{Reader: r, color: color} +} + +func (r *reader) scan() bool { + if r.scanner == nil { + r.scanner = bufio.NewScanner(r.Reader) + } + if len(r.buf) > 0 { + return true + } + if r.scanner.Scan() { + r.buf = []byte(Clean(r.scanner.Text(), r.color) + "\n") + return true + } + return false +} + +func (r *reader) fill(p []byte) int { + n := len(r.buf) + copy(p, r.buf) + if len(p) < len(r.buf) { + r.buf = r.buf[len(p):] + n = len(p) + } else { + r.buf = nil + } + return n +} + +func (r *reader) Read(p []byte) (int, error) { + n := r.fill(p) + if n < len(p) { + if !r.scan() { + if n == 0 { + return 0, io.EOF + } + return n, nil + } + n += r.fill(p[n:]) + } + return n, nil +} + +type writer struct { + io.Writer + buf []byte + color bool +} + +func NewWriter(w io.Writer, color bool) io.WriteCloser { + return &writer{Writer: w, color: color} +} + +func (w *writer) Write(p []byte) (int, error) { + buf := append(w.buf, p...) + lines := bytes.Split(buf, []byte("\n")) + if len(lines) > 0 { + last := len(lines) - 1 + w.buf = lines[last] + count := 0 + for _, line := range lines[:last] { + n, err := w.Writer.Write([]byte(Clean(string(line), w.color) + "\n")) + count += n + if err != nil { + return count, err + } + } + } + return len(p), nil +} + +func (w *writer) Close() error { + cl := Clean(string(w.buf), w.color) + _, err := w.Writer.Write([]byte(cl)) + return err +} diff --git a/vendor/github.com/lunixbochs/vtclean/line.go b/vendor/github.com/lunixbochs/vtclean/line.go new file mode 100644 index 000000000..66ee990be --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/line.go @@ -0,0 +1,113 @@ +package vtclean + +type char struct { + char byte + vt100 []byte +} + +func chars(p []byte) []char { + tmp := make([]char, len(p)) + for i, v := range p { + tmp[i].char = v + } + return tmp +} + +type lineEdit struct { + buf []char + pos, size int + vt100 []byte +} + +func newLineEdit(length int) *lineEdit { + return &lineEdit{buf: make([]char, length)} +} + +func (l *lineEdit) Vt100(p []byte) { + l.vt100 = p +} + +func (l *lineEdit) Move(x int) { + if x < 0 && l.pos <= -x { + l.pos = 0 + } else if x > 0 && l.pos+x > l.size { + l.pos = l.size + } else { + l.pos += x + } +} + +func (l *lineEdit) MoveAbs(x int) { + if x < l.size { + l.pos = x + } +} + +func (l *lineEdit) Write(p []byte) { + c := chars(p) + if len(c) > 0 { + c[0].vt100 = l.vt100 + l.vt100 = nil + } + if len(l.buf)-l.pos < len(c) { + l.buf = append(l.buf[:l.pos], c...) + } else { + copy(l.buf[l.pos:], c) + } + l.pos += len(c) + if l.pos > l.size { + l.size = l.pos + } +} + +func (l *lineEdit) Insert(p []byte) { + c := chars(p) + if len(c) > 0 { + c[0].vt100 = l.vt100 + l.vt100 = nil + } + l.size += len(c) + c = append(c, l.buf[l.pos:]...) + l.buf = append(l.buf[:l.pos], c...) +} + +func (l *lineEdit) Delete(n int) { + most := l.size - l.pos + if n > most { + n = most + } + copy(l.buf[l.pos:], l.buf[l.pos+n:]) + l.size -= n +} + +func (l *lineEdit) Clear() { + for i := 0; i < len(l.buf); i++ { + l.buf[i].char = ' ' + } +} +func (l *lineEdit) ClearLeft() { + for i := 0; i < l.pos+1; i++ { + l.buf[i].char = ' ' + } +} +func (l *lineEdit) ClearRight() { + l.size = l.pos +} + +func (l *lineEdit) Bytes() []byte { + length := 0 + buf := l.buf[:l.size] + for _, v := range buf { + length += 1 + len(v.vt100) + } + tmp := make([]byte, 0, length) + for _, v := range buf { + tmp = append(tmp, v.vt100...) + tmp = append(tmp, v.char) + } + return tmp +} + +func (l *lineEdit) String() string { + return string(l.Bytes()) +} diff --git a/vendor/github.com/lunixbochs/vtclean/vtclean.go b/vendor/github.com/lunixbochs/vtclean/vtclean.go new file mode 100644 index 000000000..64fe01fdb --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/vtclean.go @@ -0,0 +1,95 @@ +package vtclean + +import ( + "bytes" + "regexp" + "strconv" +) + +// regex based on ECMA-48: +// 1. optional: +// one of [ or ] +// any amount of 0x30-0x3f +// any amount of 0x20-0x2f +// 3. exactly one 0x40-0x7e +var vt100re = regexp.MustCompile(`^\033([\[\]]([0-9:;<=>\?]*)([!"#$%&'()*+,\-./]*))?([@A-Z\[\]^_\x60a-z{|}~])`) +var vt100exc = regexp.MustCompile(`^\033(\[[^a-zA-Z0-9@\?]+|[\(\)]).`) + +// this is to handle the RGB escape generated by `tput initc 1 500 500 500` +var vt100long = regexp.MustCompile(`^\033](\d+);([^\033]+)\033\\`) + +func Clean(line string, color bool) string { + var edit = newLineEdit(len(line)) + lineb := []byte(line) + + hadColor := false + for i := 0; i < len(lineb); { + c := lineb[i] + switch c { + case '\r': + edit.MoveAbs(0) + case '\b': + edit.Move(-1) + case '\033': + // set terminal title + if bytes.HasPrefix(lineb[i:], []byte("\x1b]0;")) { + pos := bytes.Index(lineb[i:], []byte("\a")) + if pos != -1 { + i += pos + 1 + continue + } + } + if m := vt100long.Find(lineb[i:]); m != nil { + i += len(m) + } else if m := vt100exc.Find(lineb[i:]); m != nil { + i += len(m) + } else if m := vt100re.FindSubmatch(lineb[i:]); m != nil { + i += len(m[0]) + num := string(m[2]) + n, err := strconv.Atoi(num) + if err != nil || n > 10000 { + n = 1 + } + switch m[4][0] { + case 'm': + if color { + hadColor = true + edit.Vt100(m[0]) + } + case '@': + edit.Insert(bytes.Repeat([]byte{' '}, n)) + case 'G': + edit.MoveAbs(n) + case 'C': + edit.Move(n) + case 'D': + edit.Move(-n) + case 'P': + edit.Delete(n) + case 'K': + switch num { + case "", "0": + edit.ClearRight() + case "1": + edit.ClearLeft() + case "2": + edit.Clear() + } + } + } else { + i += 1 + } + continue + default: + if c == '\n' || c == '\t' || c >= ' ' { + edit.Write([]byte{c}) + } + } + i += 1 + } + out := edit.Bytes() + if hadColor { + out = append(out, []byte("\033[0m")...) + } + return string(out) +} diff --git a/vendor/github.com/manifoldco/promptui/.gitignore b/vendor/github.com/manifoldco/promptui/.gitignore new file mode 100644 index 000000000..8ee1778b5 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.gitignore @@ -0,0 +1,3 @@ +vendor +all-cover.txt +bin/ diff --git a/vendor/github.com/manifoldco/promptui/.golangci.yml b/vendor/github.com/manifoldco/promptui/.golangci.yml new file mode 100644 index 000000000..e232bfbe5 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.golangci.yml @@ -0,0 +1,26 @@ +run: + deadline: 5m + +issues: + # Disable maximums so we see all issues + max-per-linter: 0 + max-same-issues: 0 + + # golangci-lint ignores missing docstrings by default. That's no good! + exclude-use-default: false + +linters: + disable-all: true + enable: + - misspell + - golint + - goimports + - ineffassign + - deadcode + - gofmt + - govet + - structcheck + - unconvert + - megacheck + - typecheck + - varcheck diff --git a/vendor/github.com/manifoldco/promptui/.travis.yml b/vendor/github.com/manifoldco/promptui/.travis.yml new file mode 100644 index 000000000..01c16c440 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.travis.yml @@ -0,0 +1,14 @@ +dist: bionic +language: go + +go: + - "1.12.x" + - "1.13.x" + +branches: + only: + - master + +after_success: + # only report coverage for go-version 1.11 + - if [[ $TRAVIS_GO_VERSION =~ ^1\.11 ]] ; then bash <(curl -s https://codecov.io/bash) -f all-cover.txt; fi diff --git a/vendor/github.com/manifoldco/promptui/CHANGELOG.md b/vendor/github.com/manifoldco/promptui/CHANGELOG.md new file mode 100644 index 000000000..563e9d00a --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/CHANGELOG.md @@ -0,0 +1,123 @@ +# CHANGELOG + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## Unreleased + +## [0.8.0] - 2020-09-28 + +### Added + +- Support ctrl-h for backspace +- Allow hiding entered data after submit +- Allow masking input with an empty rune to hide input length + +### Fixed + +- Fix echo of cursor after input is finished +- Better support for keycodes on Windows + + +## [0.7.0] - 2020-01-11 + +### Added + +- Add support for configurable Stdin/Stdout on Prompt +- Add support for setting initial cursor position +- Switch to golangci-lint for linting + +### Removed + +- Removed support for Go 1.11 + +### Fixed + +- Reduce tool-based deps, hopefully fixing any install issues + +## [0.6.0] - 2019-11-29 + +### Added + +- Support configurable stdin + +### Fixed + +- Correct the dep on go-i18n + +## [0.5.0] - 2019-11-29 + +### Added + +- Now building and testing on go 1.11, go 1.12, and go 1.13 + +### Removed + +- Removed support for Go versions that don't include modules. + +## [0.4.0] - 2019-02-19 + +### Added + +- The text displayed when an item was successfully selected can be hidden + +## [0.3.2] - 2018-11-26 + +### Added + +- Support Go modules + +### Fixed + +- Fix typos in PromptTemplates documentation + +## [0.3.1] - 2018-07-26 + +### Added + +- Improved documentation for GoDoc +- Navigation keys information for Windows + +### Fixed + +- `success` template was not properly displayed after a successful prompt. + +## [0.3.0] - 2018-05-22 + +### Added + +- Background colors codes and template helpers +- `AllowEdit` for prompt to prevent deletion of the default value by any key +- Added `StartInSearchMode` to allow starting the prompt in search mode + +### Fixed + +- `<Enter>` key press on Windows +- `juju/ansiterm` dependency +- `chzyer/readline#136` new api with ReadCloser +- Deleting UTF-8 characters sequence + +## [0.2.1] - 2017-11-30 + +### Fixed + +- `SelectWithAdd` panicking on `.Run` due to lack of keys setup +- Backspace key on Windows + +## [0.2.0] - 2017-11-16 + +### Added + +- `Select` items can now be searched + +## [0.1.0] - 2017-11-02 + +### Added + +- extract `promptui` from [torus](https://github.com/manifoldco/torus-cli) as a + standalone lib. +- `promptui.Prompt` provides a single input line to capture user information. +- `promptui.Select` provides a list of options to choose from. Users can + navigate through the list either one item at time or by pagination diff --git a/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md b/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..cc58cce02 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual +identity and orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behaviour and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviours that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. Representation of a +project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at +[hello@manifold.co](mailto:hello@manifold.co). All complaints will be reviewed +and investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +[http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4). diff --git a/vendor/github.com/manifoldco/promptui/LICENSE.md b/vendor/github.com/manifoldco/promptui/LICENSE.md new file mode 100644 index 000000000..3ae687b2c --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Arigato Machine Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/manifoldco/promptui/Makefile b/vendor/github.com/manifoldco/promptui/Makefile new file mode 100644 index 000000000..078a5613a --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/Makefile @@ -0,0 +1,49 @@ +export GO111MODULE := on +export PATH := ./bin:$(PATH) + +ci: bootstrap lint cover +.PHONY: ci + +################################################# +# Bootstrapping for base golang package and tool deps +################################################# + +bootstrap: + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.21.0 +.PHONY: bootstrap + +mod-update: + go get -u -m + go mod tidy + +mod-tidy: + go mod tidy + +.PHONY: $(CMD_PKGS) +.PHONY: mod-update mod-tidy + +################################################# +# Test and linting +################################################# +# Run all the linters +lint: + bin/golangci-lint run ./... +.PHONY: lint + +test: + CGO_ENABLED=0 go test $$(go list ./... | grep -v generated) +.PHONY: test + +COVER_TEST_PKGS:=$(shell find . -type f -name '*_test.go' | rev | cut -d "/" -f 2- | rev | grep -v generated | sort -u) +$(COVER_TEST_PKGS:=-cover): %-cover: all-cover.txt + @CGO_ENABLED=0 go test -v -coverprofile=$@.out -covermode=atomic ./$* + @if [ -f $@.out ]; then \ + grep -v "mode: atomic" < $@.out >> all-cover.txt; \ + rm $@.out; \ + fi + +all-cover.txt: + echo "mode: atomic" > all-cover.txt + +cover: all-cover.txt $(COVER_TEST_PKGS:=-cover) +.PHONY: cover all-cover.txt diff --git a/vendor/github.com/manifoldco/promptui/README.md b/vendor/github.com/manifoldco/promptui/README.md new file mode 100644 index 000000000..4fd4dc6d4 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/README.md @@ -0,0 +1,107 @@ +# promptui + +Interactive prompt for command-line applications. + +We built Promptui because we wanted to make it easy and fun to explore cloud +services with [manifold cli](https://github.com/manifoldco/manifold-cli). + +[Code of Conduct](./CODE_OF_CONDUCT.md) | +[Contribution Guidelines](./.github/CONTRIBUTING.md) + +[![GitHub release](https://img.shields.io/github/tag/manifoldco/promptui.svg?label=latest)](https://github.com/manifoldco/promptui/releases) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/manifoldco/promptui) +[![Travis](https://img.shields.io/travis/manifoldco/promptui/master.svg)](https://travis-ci.org/manifoldco/promptui) +[![Go Report Card](https://goreportcard.com/badge/github.com/manifoldco/promptui)](https://goreportcard.com/report/github.com/manifoldco/promptui) +[![License](https://img.shields.io/badge/license-BSD-blue.svg)](./LICENSE.md) + +## Overview + +![promptui](https://media.giphy.com/media/xUNda0Ngb5qsogLsBi/giphy.gif) + +Promptui is a library providing a simple interface to create command-line +prompts for go. It can be easily integrated into +[spf13/cobra](https://github.com/spf13/cobra), +[urfave/cli](https://github.com/urfave/cli) or any cli go application. + +Promptui has two main input modes: + +- `Prompt` provides a single line for user input. Prompt supports + optional live validation, confirmation and masking the input. + +- `Select` provides a list of options to choose from. Select supports + pagination, search, detailed view and custom templates. + +For a full list of options check [GoDoc](https://godoc.org/github.com/manifoldco/promptui). + +## Basic Usage + +### Prompt + +```go +package main + +import ( + "errors" + "fmt" + "strconv" + + "github.com/manifoldco/promptui" +) + +func main() { + validate := func(input string) error { + _, err := strconv.ParseFloat(input, 64) + if err != nil { + return errors.New("Invalid number") + } + return nil + } + + prompt := promptui.Prompt{ + Label: "Number", + Validate: validate, + } + + result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", result) +} +``` + +### Select + +```go +package main + +import ( + "fmt" + + "github.com/manifoldco/promptui" +) + +func main() { + prompt := promptui.Select{ + Label: "Select Day", + Items: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", + "Saturday", "Sunday"}, + } + + _, result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", result) +} +``` + +### More Examples + +See full list of [examples](https://github.com/manifoldco/promptui/tree/master/_examples) diff --git a/vendor/github.com/manifoldco/promptui/codes.go b/vendor/github.com/manifoldco/promptui/codes.go new file mode 100644 index 000000000..8138c40d6 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/codes.go @@ -0,0 +1,120 @@ +package promptui + +import ( + "fmt" + "strconv" + "strings" + "text/template" +) + +const esc = "\033[" + +type attribute int + +// The possible state of text inside the application, either Bold, faint, italic or underline. +// +// These constants are called through the use of the Styler function. +const ( + reset attribute = iota + + FGBold + FGFaint + FGItalic + FGUnderline +) + +// The possible colors of text inside the application. +// +// These constants are called through the use of the Styler function. +const ( + FGBlack attribute = iota + 30 + FGRed + FGGreen + FGYellow + FGBlue + FGMagenta + FGCyan + FGWhite +) + +// The possible background colors of text inside the application. +// +// These constants are called through the use of the Styler function. +const ( + BGBlack attribute = iota + 40 + BGRed + BGGreen + BGYellow + BGBlue + BGMagenta + BGCyan + BGWhite +) + +// ResetCode is the character code used to reset the terminal formatting +var ResetCode = fmt.Sprintf("%s%dm", esc, reset) + +const ( + hideCursor = esc + "?25l" + showCursor = esc + "?25h" + clearLine = esc + "2K" +) + +// FuncMap defines template helpers for the output. It can be extended as a regular map. +// +// The functions inside the map link the state, color and background colors strings detected in templates to a Styler +// function that applies the given style using the corresponding constant. +var FuncMap = template.FuncMap{ + "black": Styler(FGBlack), + "red": Styler(FGRed), + "green": Styler(FGGreen), + "yellow": Styler(FGYellow), + "blue": Styler(FGBlue), + "magenta": Styler(FGMagenta), + "cyan": Styler(FGCyan), + "white": Styler(FGWhite), + "bgBlack": Styler(BGBlack), + "bgRed": Styler(BGRed), + "bgGreen": Styler(BGGreen), + "bgYellow": Styler(BGYellow), + "bgBlue": Styler(BGBlue), + "bgMagenta": Styler(BGMagenta), + "bgCyan": Styler(BGCyan), + "bgWhite": Styler(BGWhite), + "bold": Styler(FGBold), + "faint": Styler(FGFaint), + "italic": Styler(FGItalic), + "underline": Styler(FGUnderline), +} + +func upLine(n uint) string { + return movementCode(n, 'A') +} + +func movementCode(n uint, code rune) string { + return esc + strconv.FormatUint(uint64(n), 10) + string(code) +} + +// Styler is a function that accepts multiple possible styling transforms from the state, +// color and background colors constants and transforms them into a templated string +// to apply those styles in the CLI. +// +// The returned styling function accepts a string that will be extended with +// the wrapping function's styling attributes. +func Styler(attrs ...attribute) func(interface{}) string { + attrstrs := make([]string, len(attrs)) + for i, v := range attrs { + attrstrs[i] = strconv.Itoa(int(v)) + } + + seq := strings.Join(attrstrs, ";") + + return func(v interface{}) string { + end := "" + s, ok := v.(string) + if !ok || !strings.HasSuffix(s, ResetCode) { + end = ResetCode + } + return fmt.Sprintf("%s%sm%v%s", esc, seq, v, end) + } +} diff --git a/vendor/github.com/manifoldco/promptui/cursor.go b/vendor/github.com/manifoldco/promptui/cursor.go new file mode 100644 index 000000000..7f0961bdb --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/cursor.go @@ -0,0 +1,232 @@ +package promptui + +import ( + "fmt" + "strings" +) + +// Pointer is A specific type that translates a given set of runes into a given +// set of runes pointed at by the cursor. +type Pointer func(to []rune) []rune + +func defaultCursor(ignored []rune) []rune { + return []rune("\u2588") +} + +func blockCursor(input []rune) []rune { + return []rune(fmt.Sprintf("\\e[7m%s\\e[0m", string(input))) +} + +func pipeCursor(input []rune) []rune { + marker := []rune("|") + out := []rune{} + out = append(out, marker...) + out = append(out, input...) + return out +} + +var ( + // DefaultCursor is a big square block character. Obscures whatever was + // input. + DefaultCursor Pointer = defaultCursor + // BlockCursor is a cursor which highlights a character by inverting colors + // on it. + BlockCursor Pointer = blockCursor + // PipeCursor is a pipe character "|" which appears before the input + // character. + PipeCursor Pointer = pipeCursor +) + +// Cursor tracks the state associated with the movable cursor +// The strategy is to keep the prompt, input pristine except for requested +// modifications. The insertion of the cursor happens during a `format` call +// and we read in new input via an `Update` call +type Cursor struct { + // shows where the user inserts/updates text + Cursor Pointer + // what the user entered, and what we will echo back to them, after + // insertion of the cursor and prefixing with the prompt + input []rune + // Put the cursor before this slice + Position int + erase bool +} + +// NewCursor create a new cursor, with the DefaultCursor, the specified input, +// and position at the end of the specified starting input. +func NewCursor(startinginput string, pointer Pointer, eraseDefault bool) Cursor { + if pointer == nil { + pointer = defaultCursor + } + cur := Cursor{Cursor: pointer, Position: len(startinginput), input: []rune(startinginput), erase: eraseDefault} + if eraseDefault { + cur.Start() + } else { + cur.End() + } + return cur +} + +func (c *Cursor) String() string { + return fmt.Sprintf( + "Cursor: %s, input %s, Position %d", + string(c.Cursor([]rune(""))), string(c.input), c.Position) +} + +// End is a convenience for c.Place(len(c.input)) so you don't have to know how I +// indexed. +func (c *Cursor) End() { + c.Place(len(c.input)) +} + +// Start is convenience for c.Place(0) so you don't have to know how I +// indexed. +func (c *Cursor) Start() { + c.Place(0) +} + +// ensures we are in bounds. +func (c *Cursor) correctPosition() { + if c.Position > len(c.input) { + c.Position = len(c.input) + } + + if c.Position < 0 { + c.Position = 0 + } +} + +// insert the cursor rune array into r before the provided index +func format(a []rune, c *Cursor) string { + i := c.Position + var b []rune + + out := make([]rune, 0) + if i < len(a) { + b = c.Cursor(a[i : i+1]) + out = append(out, a[:i]...) // does not include i + out = append(out, b...) // add the cursor + out = append(out, a[i+1:]...) // add the rest after i + } else { + b = c.Cursor([]rune{}) + out = append(out, a...) + out = append(out, b...) + } + return string(out) +} + +// Format renders the input with the Cursor appropriately positioned. +func (c *Cursor) Format() string { + r := c.input + // insert the cursor + return format(r, c) +} + +// FormatMask replaces all input runes with the mask rune. +func (c *Cursor) FormatMask(mask rune) string { + if mask == ' ' { + return format([]rune{}, c) + } + + r := make([]rune, len(c.input)) + for i := range r { + r[i] = mask + } + return format(r, c) +} + +// Update inserts newinput into the input []rune in the appropriate place. +// The cursor is moved to the end of the inputed sequence. +func (c *Cursor) Update(newinput string) { + a := c.input + b := []rune(newinput) + i := c.Position + a = append(a[:i], append(b, a[i:]...)...) + c.input = a + c.Move(len(b)) +} + +// Get returns a copy of the input +func (c *Cursor) Get() string { + return string(c.input) +} + +// GetMask returns a mask string with length equal to the input +func (c *Cursor) GetMask(mask rune) string { + return strings.Repeat(string(mask), len(c.input)) +} + +// Replace replaces the previous input with whatever is specified, and moves the +// cursor to the end position +func (c *Cursor) Replace(input string) { + c.input = []rune(input) + c.End() +} + +// Place moves the cursor to the absolute array index specified by position +func (c *Cursor) Place(position int) { + c.Position = position + c.correctPosition() +} + +// Move moves the cursor over in relative terms, by shift indices. +func (c *Cursor) Move(shift int) { + // delete the current cursor + c.Position = c.Position + shift + c.correctPosition() +} + +// Backspace removes the rune that precedes the cursor +// +// It handles being at the beginning or end of the row, and moves the cursor to +// the appropriate position. +func (c *Cursor) Backspace() { + a := c.input + i := c.Position + if i == 0 { + // Shrug + return + } + if i == len(a) { + c.input = a[:i-1] + } else { + c.input = append(a[:i-1], a[i:]...) + } + // now it's pointing to the i+1th element + c.Move(-1) +} + +// Listen is a readline Listener that updates internal cursor state appropriately. +func (c *Cursor) Listen(line []rune, pos int, key rune) ([]rune, int, bool) { + if line != nil { + // no matter what, update our internal representation. + c.Update(string(line)) + } + + switch key { + case 0: // empty + case KeyEnter: + return []rune(c.Get()), c.Position, false + case KeyBackspace, KeyCtrlH: + if c.erase { + c.erase = false + c.Replace("") + } + c.Backspace() + case KeyForward: + // the user wants to edit the default, despite how we set it up. Let + // them. + c.erase = false + c.Move(1) + case KeyBackward: + c.Move(-1) + default: + if c.erase { + c.erase = false + c.Replace("") + c.Update(string(key)) + } + } + + return []rune(c.Get()), c.Position, true +} diff --git a/vendor/github.com/manifoldco/promptui/go.mod b/vendor/github.com/manifoldco/promptui/go.mod new file mode 100644 index 000000000..760a44deb --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/go.mod @@ -0,0 +1,16 @@ +module github.com/manifoldco/promptui + +go 1.12 + +require ( + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a + github.com/kr/pretty v0.1.0 // indirect + github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/vendor/github.com/manifoldco/promptui/go.sum b/vendor/github.com/manifoldco/promptui/go.sum new file mode 100644 index 000000000..be5f99025 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/go.sum @@ -0,0 +1,23 @@ +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/vendor/github.com/manifoldco/promptui/keycodes.go b/vendor/github.com/manifoldco/promptui/keycodes.go new file mode 100644 index 000000000..ef5cd6f0a --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes.go @@ -0,0 +1,29 @@ +package promptui + +import "github.com/chzyer/readline" + +// These runes are used to identify the commands entered by the user in the command prompt. They map +// to specific actions of promptui in prompt mode and can be remapped if necessary. +var ( + // KeyEnter is the default key for submission/selection. + KeyEnter rune = readline.CharEnter + + // KeyCtrlH is the key for deleting input text. + KeyCtrlH rune = readline.CharCtrlH + + // KeyPrev is the default key to go up during selection. + KeyPrev rune = readline.CharPrev + KeyPrevDisplay = "↑" + + // KeyNext is the default key to go down during selection. + KeyNext rune = readline.CharNext + KeyNextDisplay = "↓" + + // KeyBackward is the default key to page up during selection. + KeyBackward rune = readline.CharBackward + KeyBackwardDisplay = "←" + + // KeyForward is the default key to page down during selection. + KeyForward rune = readline.CharForward + KeyForwardDisplay = "→" +) diff --git a/vendor/github.com/manifoldco/promptui/keycodes_other.go b/vendor/github.com/manifoldco/promptui/keycodes_other.go new file mode 100644 index 000000000..789feae4c --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes_other.go @@ -0,0 +1,10 @@ +// +build !windows + +package promptui + +import "github.com/chzyer/readline" + +var ( + // KeyBackspace is the default key for deleting input text. + KeyBackspace rune = readline.CharBackspace +) diff --git a/vendor/github.com/manifoldco/promptui/keycodes_windows.go b/vendor/github.com/manifoldco/promptui/keycodes_windows.go new file mode 100644 index 000000000..737d1566e --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes_windows.go @@ -0,0 +1,10 @@ +// +build windows + +package promptui + +// source: https://msdn.microsoft.com/en-us/library/aa243025(v=vs.60).aspx + +var ( + // KeyBackspace is the default key for deleting input text inside a command line prompt. + KeyBackspace rune = 8 +) diff --git a/vendor/github.com/manifoldco/promptui/list/list.go b/vendor/github.com/manifoldco/promptui/list/list.go new file mode 100644 index 000000000..c98a39cfa --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/list/list.go @@ -0,0 +1,237 @@ +package list + +import ( + "fmt" + "reflect" + "strings" +) + +// Searcher is a base function signature that is used inside select when activating the search mode. +// If defined, it is called on each items of the select and should return a boolean for whether or not +// the item fits the searched term. +type Searcher func(input string, index int) bool + +// NotFound is an index returned when no item was selected. This could +// happen due to a search without results. +const NotFound = -1 + +// List holds a collection of items that can be displayed with an N number of +// visible items. The list can be moved up, down by one item of time or an +// entire page (ie: visible size). It keeps track of the current selected item. +type List struct { + items []*interface{} + scope []*interface{} + cursor int // cursor holds the index of the current selected item + size int // size is the number of visible options + start int + Searcher Searcher +} + +// New creates and initializes a list of searchable items. The items attribute must be a slice type with a +// size greater than 0. Error will be returned if those two conditions are not met. +func New(items interface{}, size int) (*List, error) { + if size < 1 { + return nil, fmt.Errorf("list size %d must be greater than 0", size) + } + + if items == nil || reflect.TypeOf(items).Kind() != reflect.Slice { + return nil, fmt.Errorf("items %v is not a slice", items) + } + + slice := reflect.ValueOf(items) + values := make([]*interface{}, slice.Len()) + + for i := range values { + item := slice.Index(i).Interface() + values[i] = &item + } + + return &List{size: size, items: values, scope: values}, nil +} + +// Prev moves the visible list back one item. If the selected item is out of +// view, the new select item becomes the last visible item. If the list is +// already at the top, nothing happens. +func (l *List) Prev() { + if l.cursor > 0 { + l.cursor-- + } + + if l.start > l.cursor { + l.start = l.cursor + } +} + +// Search allows the list to be filtered by a given term. The list must +// implement the searcher function signature for this functionality to work. +func (l *List) Search(term string) { + term = strings.Trim(term, " ") + l.cursor = 0 + l.start = 0 + l.search(term) +} + +// CancelSearch stops the current search and returns the list to its +// original order. +func (l *List) CancelSearch() { + l.cursor = 0 + l.start = 0 + l.scope = l.items +} + +func (l *List) search(term string) { + var scope []*interface{} + + for i, item := range l.items { + if l.Searcher(term, i) { + scope = append(scope, item) + } + } + + l.scope = scope +} + +// Start returns the current render start position of the list. +func (l *List) Start() int { + return l.start +} + +// SetStart sets the current scroll position. Values out of bounds will be +// clamped. +func (l *List) SetStart(i int) { + if i < 0 { + i = 0 + } + if i > l.cursor { + l.start = l.cursor + } else { + l.start = i + } +} + +// SetCursor sets the position of the cursor in the list. Values out of bounds +// will be clamped. +func (l *List) SetCursor(i int) { + max := len(l.scope) - 1 + if i >= max { + i = max + } + if i < 0 { + i = 0 + } + l.cursor = i + + if l.start > l.cursor { + l.start = l.cursor + } else if l.start+l.size <= l.cursor { + l.start = l.cursor - l.size + 1 + } +} + +// Next moves the visible list forward one item. If the selected item is out of +// view, the new select item becomes the first visible item. If the list is +// already at the bottom, nothing happens. +func (l *List) Next() { + max := len(l.scope) - 1 + + if l.cursor < max { + l.cursor++ + } + + if l.start+l.size <= l.cursor { + l.start = l.cursor - l.size + 1 + } +} + +// PageUp moves the visible list backward by x items. Where x is the size of the +// visible items on the list. The selected item becomes the first visible item. +// If the list is already at the bottom, the selected item becomes the last +// visible item. +func (l *List) PageUp() { + start := l.start - l.size + if start < 0 { + l.start = 0 + } else { + l.start = start + } + + cursor := l.start + + if cursor < l.cursor { + l.cursor = cursor + } +} + +// PageDown moves the visible list forward by x items. Where x is the size of +// the visible items on the list. The selected item becomes the first visible +// item. +func (l *List) PageDown() { + start := l.start + l.size + max := len(l.scope) - l.size + + switch { + case len(l.scope) < l.size: + l.start = 0 + case start > max: + l.start = max + default: + l.start = start + } + + cursor := l.start + + if cursor == l.cursor { + l.cursor = len(l.scope) - 1 + } else if cursor > l.cursor { + l.cursor = cursor + } +} + +// CanPageDown returns whether a list can still PageDown(). +func (l *List) CanPageDown() bool { + max := len(l.scope) + return l.start+l.size < max +} + +// CanPageUp returns whether a list can still PageUp(). +func (l *List) CanPageUp() bool { + return l.start > 0 +} + +// Index returns the index of the item currently selected inside the searched list. If no item is selected, +// the NotFound (-1) index is returned. +func (l *List) Index() int { + selected := l.scope[l.cursor] + + for i, item := range l.items { + if item == selected { + return i + } + } + + return NotFound +} + +// Items returns a slice equal to the size of the list with the current visible +// items and the index of the active item in this list. +func (l *List) Items() ([]interface{}, int) { + var result []interface{} + max := len(l.scope) + end := l.start + l.size + + if end > max { + end = max + } + + active := NotFound + + for i, j := l.start, 0; i < end; i, j = i+1, j+1 { + if l.cursor == i { + active = j + } + + result = append(result, *l.scope[i]) + } + + return result, active +} diff --git a/vendor/github.com/manifoldco/promptui/prompt.go b/vendor/github.com/manifoldco/promptui/prompt.go new file mode 100644 index 000000000..8e35123b0 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/prompt.go @@ -0,0 +1,341 @@ +package promptui + +import ( + "fmt" + "io" + "strings" + "text/template" + + "github.com/chzyer/readline" + "github.com/manifoldco/promptui/screenbuf" +) + +// Prompt represents a single line text field input with options for validation and input masks. +type Prompt struct { + // Label is the value displayed on the command line prompt. + // + // The value for Label can be a simple string or a struct that will need to be accessed by dot notation + // inside the templates. For example, `{{ .Name }}` will display the name property of a struct. + Label interface{} + + // Default is the initial value for the prompt. This value will be displayed next to the prompt's label + // and the user will be able to view or change it depending on the options. + Default string + + // AllowEdit lets the user edit the default value. If false, any key press + // other than <Enter> automatically clears the default value. + AllowEdit bool + + // Validate is an optional function that fill be used against the entered value in the prompt to validate it. + Validate ValidateFunc + + // Mask is an optional rune that sets which character to display instead of the entered characters. This + // allows hiding private information like passwords. + Mask rune + + // HideEntered sets whether to hide the text after the user has pressed enter. + HideEntered bool + + // Templates can be used to customize the prompt output. If nil is passed, the + // default templates are used. See the PromptTemplates docs for more info. + Templates *PromptTemplates + + // IsConfirm makes the prompt ask for a yes or no ([Y/N]) question rather than request an input. When set, + // most properties related to input will be ignored. + IsConfirm bool + + // IsVimMode enables vi-like movements (hjkl) and editing. + IsVimMode bool + + // the Pointer defines how to render the cursor. + Pointer Pointer + + Stdin io.ReadCloser + Stdout io.WriteCloser +} + +// PromptTemplates allow a prompt to be customized following stdlib +// text/template syntax. Custom state, colors and background color are available for use inside +// the templates and are documented inside the Variable section of the docs. +// +// Examples +// +// text/templates use a special notation to display programmable content. Using the double bracket notation, +// the value can be printed with specific helper functions. For example +// +// This displays the value given to the template as pure, unstylized text. +// '{{ . }}' +// +// This displays the value colored in cyan +// '{{ . | cyan }}' +// +// This displays the value colored in red with a cyan background-color +// '{{ . | red | cyan }}' +// +// See the doc of text/template for more info: https://golang.org/pkg/text/template/ +type PromptTemplates struct { + // Prompt is a text/template for the prompt label displayed on the left side of the prompt. + Prompt string + + // Prompt is a text/template for the prompt label when IsConfirm is set as true. + Confirm string + + // Valid is a text/template for the prompt label when the value entered is valid. + Valid string + + // Invalid is a text/template for the prompt label when the value entered is invalid. + Invalid string + + // Success is a text/template for the prompt label when the user has pressed entered and the value has been + // deemed valid by the validation function. The label will keep using this template even when the prompt ends + // inside the console. + Success string + + // Prompt is a text/template for the prompt label when the value is invalid due to an error triggered by + // the prompt's validation function. + ValidationError string + + // FuncMap is a map of helper functions that can be used inside of templates according to the text/template + // documentation. + // + // By default, FuncMap contains the color functions used to color the text in templates. If FuncMap + // is overridden, the colors functions must be added in the override from promptui.FuncMap to work. + FuncMap template.FuncMap + + prompt *template.Template + valid *template.Template + invalid *template.Template + validation *template.Template + success *template.Template +} + +// Run executes the prompt. Its displays the label and default value if any, asking the user to enter a value. +// Run will keep the prompt alive until it has been canceled from the command prompt or it has received a valid +// value. It will return the value and an error if any occurred during the prompt's execution. +func (p *Prompt) Run() (string, error) { + var err error + + err = p.prepareTemplates() + if err != nil { + return "", err + } + + c := &readline.Config{ + Stdin: p.Stdin, + Stdout: p.Stdout, + EnableMask: p.Mask != 0, + MaskRune: p.Mask, + HistoryLimit: -1, + VimMode: p.IsVimMode, + UniqueEditLine: true, + } + + err = c.Init() + if err != nil { + return "", err + } + + rl, err := readline.NewEx(c) + if err != nil { + return "", err + } + // we're taking over the cursor, so stop showing it. + rl.Write([]byte(hideCursor)) + sb := screenbuf.New(rl) + + validFn := func(x string) error { + return nil + } + if p.Validate != nil { + validFn = p.Validate + } + + var inputErr error + input := p.Default + if p.IsConfirm { + input = "" + } + eraseDefault := input != "" && !p.AllowEdit + cur := NewCursor(input, p.Pointer, eraseDefault) + + listen := func(input []rune, pos int, key rune) ([]rune, int, bool) { + _, _, keepOn := cur.Listen(input, pos, key) + err := validFn(cur.Get()) + var prompt []byte + + if err != nil { + prompt = render(p.Templates.invalid, p.Label) + } else { + prompt = render(p.Templates.valid, p.Label) + if p.IsConfirm { + prompt = render(p.Templates.prompt, p.Label) + } + } + + echo := cur.Format() + if p.Mask != 0 { + echo = cur.FormatMask(p.Mask) + } + + prompt = append(prompt, []byte(echo)...) + sb.Reset() + sb.Write(prompt) + if inputErr != nil { + validation := render(p.Templates.validation, inputErr) + sb.Write(validation) + inputErr = nil + } + sb.Flush() + return nil, 0, keepOn + } + + c.SetListener(listen) + + for { + _, err = rl.Readline() + inputErr = validFn(cur.Get()) + if inputErr == nil { + break + } + + if err != nil { + break + } + } + + if err != nil { + switch err { + case readline.ErrInterrupt: + err = ErrInterrupt + case io.EOF: + err = ErrEOF + } + if err.Error() == "Interrupt" { + err = ErrInterrupt + } + sb.Reset() + sb.WriteString("") + sb.Flush() + rl.Write([]byte(showCursor)) + rl.Close() + return "", err + } + + echo := cur.Get() + if p.Mask != 0 { + echo = cur.GetMask(p.Mask) + } + + prompt := render(p.Templates.success, p.Label) + prompt = append(prompt, []byte(echo)...) + + if p.IsConfirm { + lowerDefault := strings.ToLower(p.Default) + if strings.ToLower(cur.Get()) != "y" && (lowerDefault != "y" || (lowerDefault == "y" && cur.Get() != "")) { + prompt = render(p.Templates.invalid, p.Label) + err = ErrAbort + } + } + + if p.HideEntered { + clearScreen(sb) + } else { + sb.Reset() + sb.Write(prompt) + sb.Flush() + } + + rl.Write([]byte(showCursor)) + rl.Close() + + return cur.Get(), err +} + +func (p *Prompt) prepareTemplates() error { + tpls := p.Templates + if tpls == nil { + tpls = &PromptTemplates{} + } + + if tpls.FuncMap == nil { + tpls.FuncMap = FuncMap + } + + bold := Styler(FGBold) + + if p.IsConfirm { + if tpls.Confirm == "" { + confirm := "y/N" + if strings.ToLower(p.Default) == "y" { + confirm = "Y/n" + } + tpls.Confirm = fmt.Sprintf(`{{ "%s" | bold }} {{ . | bold }}? {{ "[%s]" | faint }} `, IconInitial, confirm) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Confirm) + if err != nil { + return err + } + + tpls.prompt = tpl + } else { + if tpls.Prompt == "" { + tpls.Prompt = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconInitial), bold(":")) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Prompt) + if err != nil { + return err + } + + tpls.prompt = tpl + } + + if tpls.Valid == "" { + tpls.Valid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconGood), bold(":")) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Valid) + if err != nil { + return err + } + + tpls.valid = tpl + + if tpls.Invalid == "" { + tpls.Invalid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconBad), bold(":")) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Invalid) + if err != nil { + return err + } + + tpls.invalid = tpl + + if tpls.ValidationError == "" { + tpls.ValidationError = `{{ ">>" | red }} {{ . | red }}` + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.ValidationError) + if err != nil { + return err + } + + tpls.validation = tpl + + if tpls.Success == "" { + tpls.Success = fmt.Sprintf("{{ . | faint }}%s ", Styler(FGFaint)(":")) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Success) + if err != nil { + return err + } + + tpls.success = tpl + + p.Templates = tpls + + return nil +} diff --git a/vendor/github.com/manifoldco/promptui/promptui.go b/vendor/github.com/manifoldco/promptui/promptui.go new file mode 100644 index 000000000..6248e5859 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/promptui.go @@ -0,0 +1,27 @@ +// Package promptui is a library providing a simple interface to create command-line prompts for go. +// It can be easily integrated into spf13/cobra, urfave/cli or any cli go application. +// +// promptui has two main input modes: +// +// Prompt provides a single line for user input. It supports optional live validation, +// confirmation and masking the input. +// +// Select provides a list of options to choose from. It supports pagination, search, +// detailed view and custom templates. +package promptui + +import "errors" + +// ErrEOF is the error returned from prompts when EOF is encountered. +var ErrEOF = errors.New("^D") + +// ErrInterrupt is the error returned from prompts when an interrupt (ctrl-c) is +// encountered. +var ErrInterrupt = errors.New("^C") + +// ErrAbort is the error returned when confirm prompts are supplied "n" +var ErrAbort = errors.New("") + +// ValidateFunc is a placeholder type for any validation functions that validates a given input. It should return +// a ValidationError if the input is not valid. +type ValidateFunc func(string) error diff --git a/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go b/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go new file mode 100644 index 000000000..861390045 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go @@ -0,0 +1,151 @@ +package screenbuf + +import ( + "bytes" + "fmt" + "io" +) + +const esc = "\033[" + +var ( + clearLine = []byte(esc + "2K\r") + moveUp = []byte(esc + "1A") + moveDown = []byte(esc + "1B") +) + +// ScreenBuf is a convenient way to write to terminal screens. It creates, +// clears and, moves up or down lines as needed to write the output to the +// terminal using ANSI escape codes. +type ScreenBuf struct { + w io.Writer + buf *bytes.Buffer + reset bool + cursor int + height int +} + +// New creates and initializes a new ScreenBuf. +func New(w io.Writer) *ScreenBuf { + return &ScreenBuf{buf: &bytes.Buffer{}, w: w} +} + +// Reset truncates the underlining buffer and marks all its previous lines to be +// cleared during the next Write. +func (s *ScreenBuf) Reset() { + s.buf.Reset() + s.reset = true +} + +// Clear clears all previous lines and the output starts from the top. +func (s *ScreenBuf) Clear() error { + for i := 0; i < s.height; i++ { + _, err := s.buf.Write(moveUp) + if err != nil { + return err + } + _, err = s.buf.Write(clearLine) + if err != nil { + return err + } + } + s.cursor = 0 + s.height = 0 + s.reset = false + return nil +} + +// Write writes a single line to the underlining buffer. If the ScreenBuf was +// previously reset, all previous lines are cleared and the output starts from +// the top. Lines with \r or \n will cause an error since they can interfere with the +// terminal ability to move between lines. +func (s *ScreenBuf) Write(b []byte) (int, error) { + if bytes.ContainsAny(b, "\r\n") { + return 0, fmt.Errorf("%q should not contain either \\r or \\n", b) + } + + if s.reset { + if err := s.Clear(); err != nil { + return 0, err + } + } + + switch { + case s.cursor == s.height: + n, err := s.buf.Write(clearLine) + if err != nil { + return n, err + } + + n, err = s.buf.Write(b) + if err != nil { + return n, err + } + + _, err = s.buf.Write([]byte("\n")) + if err != nil { + return n, err + } + + s.height++ + s.cursor++ + return n, nil + case s.cursor < s.height: + n, err := s.buf.Write(clearLine) + if err != nil { + return n, err + } + n, err = s.buf.Write(b) + if err != nil { + return n, err + } + n, err = s.buf.Write(moveDown) + if err != nil { + return n, err + } + s.cursor++ + return n, nil + default: + return 0, fmt.Errorf("Invalid write cursor position (%d) exceeded line height: %d", s.cursor, s.height) + } +} + +// Flush writes any buffered data to the underlying io.Writer, ensuring that any pending data is displayed. +func (s *ScreenBuf) Flush() error { + for i := s.cursor; i < s.height; i++ { + if i < s.height { + _, err := s.buf.Write(clearLine) + if err != nil { + return err + } + } + _, err := s.buf.Write(moveDown) + if err != nil { + return err + } + } + + _, err := s.buf.WriteTo(s.w) + if err != nil { + return err + } + + s.buf.Reset() + + for i := 0; i < s.height; i++ { + _, err := s.buf.Write(moveUp) + if err != nil { + return err + } + } + + s.cursor = 0 + + return nil +} + +// WriteString is a convenient function to write a new line passing a string. +// Check ScreenBuf.Write() for a detailed explanation of the function behaviour. +func (s *ScreenBuf) WriteString(str string) (int, error) { + return s.Write([]byte(str)) +} diff --git a/vendor/github.com/manifoldco/promptui/select.go b/vendor/github.com/manifoldco/promptui/select.go new file mode 100644 index 000000000..19b9e0c2e --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/select.go @@ -0,0 +1,637 @@ +package promptui + +import ( + "bytes" + "fmt" + "io" + "os" + "text/template" + + "github.com/chzyer/readline" + "github.com/juju/ansiterm" + "github.com/manifoldco/promptui/list" + "github.com/manifoldco/promptui/screenbuf" +) + +// SelectedAdd is used internally inside SelectWithAdd when the add option is selected in select mode. +// Since -1 is not a possible selected index, this ensure that add mode is always unique inside +// SelectWithAdd's logic. +const SelectedAdd = -1 + +// Select represents a list of items used to enable selections, they can be used as search engines, menus +// or as a list of items in a cli based prompt. +type Select struct { + // Label is the text displayed on top of the list to direct input. The IconInitial value "?" will be + // appended automatically to the label so it does not need to be added. + // + // The value for Label can be a simple string or a struct that will need to be accessed by dot notation + // inside the templates. For example, `{{ .Name }}` will display the name property of a struct. + Label interface{} + + // Items are the items to display inside the list. It expect a slice of any kind of values, including strings. + // + // If using a slice of strings, promptui will use those strings directly into its base templates or the + // provided templates. If using any other type in the slice, it will attempt to transform it into a string + // before giving it to its templates. Custom templates will override this behavior if using the dot notation + // inside the templates. + // + // For example, `{{ .Name }}` will display the name property of a struct. + Items interface{} + + // Size is the number of items that should appear on the select before scrolling is necessary. Defaults to 5. + Size int + + // CursorPos is the initial position of the cursor. + CursorPos int + + // IsVimMode sets whether to use vim mode when using readline in the command prompt. Look at + // https://godoc.org/github.com/chzyer/readline#Config for more information on readline. + IsVimMode bool + + // HideHelp sets whether to hide help information. + HideHelp bool + + // HideSelected sets whether to hide the text displayed after an item is successfully selected. + HideSelected bool + + // Templates can be used to customize the select output. If nil is passed, the + // default templates are used. See the SelectTemplates docs for more info. + Templates *SelectTemplates + + // Keys is the set of keys used in select mode to control the command line interface. See the SelectKeys docs for + // more info. + Keys *SelectKeys + + // Searcher is a function that can be implemented to refine the base searching algorithm in selects. + // + // Search is a function that will receive the searched term and the item's index and should return a boolean + // for whether or not the terms are alike. It is unimplemented by default and search will not work unless + // it is implemented. + Searcher list.Searcher + + // StartInSearchMode sets whether or not the select mode should start in search mode or selection mode. + // For search mode to work, the Search property must be implemented. + StartInSearchMode bool + + list *list.List + + // A function that determines how to render the cursor + Pointer Pointer + + Stdin io.ReadCloser + Stdout io.WriteCloser +} + +// SelectKeys defines the available keys used by select mode to enable the user to move around the list +// and trigger search mode. See the Key struct docs for more information on keys. +type SelectKeys struct { + // Next is the key used to move to the next element inside the list. Defaults to down arrow key. + Next Key + + // Prev is the key used to move to the previous element inside the list. Defaults to up arrow key. + Prev Key + + // PageUp is the key used to jump back to the first element inside the list. Defaults to left arrow key. + PageUp Key + + // PageUp is the key used to jump forward to the last element inside the list. Defaults to right arrow key. + PageDown Key + + // Search is the key used to trigger the search mode for the list. Default to the "/" key. + Search Key +} + +// Key defines a keyboard code and a display representation for the help menu. +type Key struct { + // Code is a rune that will be used to compare against typed keys with readline. + // Check https://github.com/chzyer/readline for a list of codes + Code rune + + // Display is the string that will be displayed inside the help menu to help inform the user + // of which key to use on his keyboard for various functions. + Display string +} + +// SelectTemplates allow a select list to be customized following stdlib +// text/template syntax. Custom state, colors and background color are available for use inside +// the templates and are documented inside the Variable section of the docs. +// +// Examples +// +// text/templates use a special notation to display programmable content. Using the double bracket notation, +// the value can be printed with specific helper functions. For example +// +// This displays the value given to the template as pure, unstylized text. Structs are transformed to string +// with this notation. +// '{{ . }}' +// +// This displays the name property of the value colored in cyan +// '{{ .Name | cyan }}' +// +// This displays the label property of value colored in red with a cyan background-color +// '{{ .Label | red | cyan }}' +// +// See the doc of text/template for more info: https://golang.org/pkg/text/template/ +// +// Notes +// +// Setting any of these templates will remove the icons from the default templates. They must +// be added back in each of their specific templates. The styles.go constants contains the default icons. +type SelectTemplates struct { + // Label is a text/template for the main command line label. Defaults to printing the label as it with + // the IconInitial. + Label string + + // Active is a text/template for when an item is currently active within the list. + Active string + + // Inactive is a text/template for when an item is not currently active inside the list. This + // template is used for all items unless they are active or selected. + Inactive string + + // Selected is a text/template for when an item was successfully selected. + Selected string + + // Details is a text/template for when an item current active to show + // additional information. It can have multiple lines. + // + // Detail will always be displayed for the active element and thus can be used to display additional + // information on the element beyond its label. + // + // promptui will not trim spaces and tabs will be displayed if the template is indented. + Details string + + // Help is a text/template for displaying instructions at the top. By default + // it shows keys for movement and search. + Help string + + // FuncMap is a map of helper functions that can be used inside of templates according to the text/template + // documentation. + // + // By default, FuncMap contains the color functions used to color the text in templates. If FuncMap + // is overridden, the colors functions must be added in the override from promptui.FuncMap to work. + FuncMap template.FuncMap + + label *template.Template + active *template.Template + inactive *template.Template + selected *template.Template + details *template.Template + help *template.Template +} + +// SearchPrompt is the prompt displayed in search mode. +var SearchPrompt = "Search: " + +// Run executes the select list. It displays the label and the list of items, asking the user to chose any +// value within to list. Run will keep the prompt alive until it has been canceled from +// the command prompt or it has received a valid value. It will return the value and an error if any +// occurred during the select's execution. +func (s *Select) Run() (int, string, error) { + return s.RunCursorAt(s.CursorPos, 0) +} + +// RunCursorAt executes the select list, initializing the cursor to the given +// position. Invalid cursor positions will be clamped to valid values. It +// displays the label and the list of items, asking the user to chose any value +// within to list. Run will keep the prompt alive until it has been canceled +// from the command prompt or it has received a valid value. It will return +// the value and an error if any occurred during the select's execution. +func (s *Select) RunCursorAt(cursorPos, scroll int) (int, string, error) { + if s.Size == 0 { + s.Size = 5 + } + + l, err := list.New(s.Items, s.Size) + if err != nil { + return 0, "", err + } + l.Searcher = s.Searcher + + s.list = l + + s.setKeys() + + err = s.prepareTemplates() + if err != nil { + return 0, "", err + } + return s.innerRun(cursorPos, scroll, ' ') +} + +func (s *Select) innerRun(cursorPos, scroll int, top rune) (int, string, error) { + c := &readline.Config{ + Stdin: s.Stdin, + Stdout: s.Stdout, + } + err := c.Init() + if err != nil { + return 0, "", err + } + + c.Stdin = readline.NewCancelableStdin(c.Stdin) + + if s.IsVimMode { + c.VimMode = true + } + + c.HistoryLimit = -1 + c.UniqueEditLine = true + + rl, err := readline.NewEx(c) + if err != nil { + return 0, "", err + } + + rl.Write([]byte(hideCursor)) + sb := screenbuf.New(rl) + + cur := NewCursor("", s.Pointer, false) + + canSearch := s.Searcher != nil + searchMode := s.StartInSearchMode + s.list.SetCursor(cursorPos) + s.list.SetStart(scroll) + + c.SetListener(func(line []rune, pos int, key rune) ([]rune, int, bool) { + switch { + case key == KeyEnter: + return nil, 0, true + case key == s.Keys.Next.Code || (key == 'j' && !searchMode): + s.list.Next() + case key == s.Keys.Prev.Code || (key == 'k' && !searchMode): + s.list.Prev() + case key == s.Keys.Search.Code: + if !canSearch { + break + } + + if searchMode { + searchMode = false + cur.Replace("") + s.list.CancelSearch() + } else { + searchMode = true + } + case key == KeyBackspace || key == KeyCtrlH: + if !canSearch || !searchMode { + break + } + + cur.Backspace() + if len(cur.Get()) > 0 { + s.list.Search(cur.Get()) + } else { + s.list.CancelSearch() + } + case key == s.Keys.PageUp.Code || (key == 'h' && !searchMode): + s.list.PageUp() + case key == s.Keys.PageDown.Code || (key == 'l' && !searchMode): + s.list.PageDown() + default: + if canSearch && searchMode { + cur.Update(string(line)) + s.list.Search(cur.Get()) + } + } + + if searchMode { + header := SearchPrompt + cur.Format() + sb.WriteString(header) + } else if !s.HideHelp { + help := s.renderHelp(canSearch) + sb.Write(help) + } + + label := render(s.Templates.label, s.Label) + sb.Write(label) + + items, idx := s.list.Items() + last := len(items) - 1 + + for i, item := range items { + page := " " + + switch i { + case 0: + if s.list.CanPageUp() { + page = "↑" + } else { + page = string(top) + } + case last: + if s.list.CanPageDown() { + page = "↓" + } + } + + output := []byte(page + " ") + + if i == idx { + output = append(output, render(s.Templates.active, item)...) + } else { + output = append(output, render(s.Templates.inactive, item)...) + } + + sb.Write(output) + } + + if idx == list.NotFound { + sb.WriteString("") + sb.WriteString("No results") + } else { + active := items[idx] + + details := s.renderDetails(active) + for _, d := range details { + sb.Write(d) + } + } + + sb.Flush() + + return nil, 0, true + }) + + for { + _, err = rl.Readline() + + if err != nil { + switch { + case err == readline.ErrInterrupt, err.Error() == "Interrupt": + err = ErrInterrupt + case err == io.EOF: + err = ErrEOF + } + break + } + + _, idx := s.list.Items() + if idx != list.NotFound { + break + } + + } + + if err != nil { + if err.Error() == "Interrupt" { + err = ErrInterrupt + } + sb.Reset() + sb.WriteString("") + sb.Flush() + rl.Write([]byte(showCursor)) + rl.Close() + return 0, "", err + } + + items, idx := s.list.Items() + item := items[idx] + + if s.HideSelected { + clearScreen(sb) + } else { + sb.Reset() + sb.Write(render(s.Templates.selected, item)) + sb.Flush() + } + + rl.Write([]byte(showCursor)) + rl.Close() + + return s.list.Index(), fmt.Sprintf("%v", item), err +} + +// ScrollPosition returns the current scroll position. +func (s *Select) ScrollPosition() int { + return s.list.Start() +} + +func (s *Select) prepareTemplates() error { + tpls := s.Templates + if tpls == nil { + tpls = &SelectTemplates{} + } + + if tpls.FuncMap == nil { + tpls.FuncMap = FuncMap + } + + if tpls.Label == "" { + tpls.Label = fmt.Sprintf("%s {{.}}: ", IconInitial) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Label) + if err != nil { + return err + } + + tpls.label = tpl + + if tpls.Active == "" { + tpls.Active = fmt.Sprintf("%s {{ . | underline }}", IconSelect) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Active) + if err != nil { + return err + } + + tpls.active = tpl + + if tpls.Inactive == "" { + tpls.Inactive = " {{.}}" + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Inactive) + if err != nil { + return err + } + + tpls.inactive = tpl + + if tpls.Selected == "" { + tpls.Selected = fmt.Sprintf(`{{ "%s" | green }} {{ . | faint }}`, IconGood) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Selected) + if err != nil { + return err + } + tpls.selected = tpl + + if tpls.Details != "" { + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Details) + if err != nil { + return err + } + + tpls.details = tpl + } + + if tpls.Help == "" { + tpls.Help = fmt.Sprintf(`{{ "Use the arrow keys to navigate:" | faint }} {{ .NextKey | faint }} ` + + `{{ .PrevKey | faint }} {{ .PageDownKey | faint }} {{ .PageUpKey | faint }} ` + + `{{ if .Search }} {{ "and" | faint }} {{ .SearchKey | faint }} {{ "toggles search" | faint }}{{ end }}`) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Help) + if err != nil { + return err + } + + tpls.help = tpl + + s.Templates = tpls + + return nil +} + +// SelectWithAdd represents a list for selecting a single item inside a list of items with the possibility to +// add new items to the list. +type SelectWithAdd struct { + // Label is the text displayed on top of the list to direct input. The IconInitial value "?" will be + // appended automatically to the label so it does not need to be added. + Label string + + // Items are the items to display inside the list. Each item will be listed individually with the + // AddLabel as the first item of the list. + Items []string + + // AddLabel is the label used for the first item of the list that enables adding a new item. + // Selecting this item in the list displays the add item prompt using promptui/prompt. + AddLabel string + + // Validate is an optional function that fill be used against the entered value in the prompt to validate it. + // If the value is valid, it is returned to the callee to be added in the list. + Validate ValidateFunc + + // IsVimMode sets whether to use vim mode when using readline in the command prompt. Look at + // https://godoc.org/github.com/chzyer/readline#Config for more information on readline. + IsVimMode bool + + // a function that defines how to render the cursor + Pointer Pointer + + // HideHelp sets whether to hide help information. + HideHelp bool +} + +// Run executes the select list. Its displays the label and the list of items, asking the user to chose any +// value within to list or add his own. Run will keep the prompt alive until it has been canceled from +// the command prompt or it has received a valid value. +// +// If the addLabel is selected in the list, this function will return a -1 index with the added label and no error. +// Otherwise, it will return the index and the value of the selected item. In any case, if an error is triggered, it +// will also return the error as its third return value. +func (sa *SelectWithAdd) Run() (int, string, error) { + if len(sa.Items) > 0 { + newItems := append([]string{sa.AddLabel}, sa.Items...) + + list, err := list.New(newItems, 5) + if err != nil { + return 0, "", err + } + + s := Select{ + Label: sa.Label, + Items: newItems, + IsVimMode: sa.IsVimMode, + HideHelp: sa.HideHelp, + Size: 5, + list: list, + Pointer: sa.Pointer, + } + s.setKeys() + + err = s.prepareTemplates() + if err != nil { + return 0, "", err + } + + selected, value, err := s.innerRun(1, 0, '+') + if err != nil || selected != 0 { + return selected - 1, value, err + } + + // XXX run through terminal for windows + os.Stdout.Write([]byte(upLine(1) + "\r" + clearLine)) + } + + p := Prompt{ + Label: sa.AddLabel, + Validate: sa.Validate, + IsVimMode: sa.IsVimMode, + Pointer: sa.Pointer, + } + value, err := p.Run() + return SelectedAdd, value, err +} + +func (s *Select) setKeys() { + if s.Keys != nil { + return + } + s.Keys = &SelectKeys{ + Prev: Key{Code: KeyPrev, Display: KeyPrevDisplay}, + Next: Key{Code: KeyNext, Display: KeyNextDisplay}, + PageUp: Key{Code: KeyBackward, Display: KeyBackwardDisplay}, + PageDown: Key{Code: KeyForward, Display: KeyForwardDisplay}, + Search: Key{Code: '/', Display: "/"}, + } +} + +func (s *Select) renderDetails(item interface{}) [][]byte { + if s.Templates.details == nil { + return nil + } + + var buf bytes.Buffer + w := ansiterm.NewTabWriter(&buf, 0, 0, 8, ' ', 0) + + err := s.Templates.details.Execute(w, item) + if err != nil { + fmt.Fprintf(w, "%v", item) + } + + w.Flush() + + output := buf.Bytes() + + return bytes.Split(output, []byte("\n")) +} + +func (s *Select) renderHelp(b bool) []byte { + keys := struct { + NextKey string + PrevKey string + PageDownKey string + PageUpKey string + Search bool + SearchKey string + }{ + NextKey: s.Keys.Next.Display, + PrevKey: s.Keys.Prev.Display, + PageDownKey: s.Keys.PageDown.Display, + PageUpKey: s.Keys.PageUp.Display, + SearchKey: s.Keys.Search.Display, + Search: b, + } + + return render(s.Templates.help, keys) +} + +func render(tpl *template.Template, data interface{}) []byte { + var buf bytes.Buffer + err := tpl.Execute(&buf, data) + if err != nil { + return []byte(fmt.Sprintf("%v", data)) + } + return buf.Bytes() +} + +func clearScreen(sb *screenbuf.ScreenBuf) { + sb.Reset() + sb.Clear() + sb.Flush() +} diff --git a/vendor/github.com/manifoldco/promptui/styles.go b/vendor/github.com/manifoldco/promptui/styles.go new file mode 100644 index 000000000..d7698c9cf --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/styles.go @@ -0,0 +1,23 @@ +// +build !windows + +package promptui + +// These are the default icons used by promptui for select and prompts. These should not be overridden and instead +// customized through the use of custom templates +var ( + // IconInitial is the icon used when starting in prompt mode and the icon next to the label when + // starting in select mode. + IconInitial = Styler(FGBlue)("?") + + // IconGood is the icon used when a good answer is entered in prompt mode. + IconGood = Styler(FGGreen)("✔") + + // IconWarn is the icon used when a good, but potentially invalid answer is entered in prompt mode. + IconWarn = Styler(FGYellow)("⚠") + + // IconBad is the icon used when a bad answer is entered in prompt mode. + IconBad = Styler(FGRed)("✗") + + // IconSelect is the icon used to identify the currently selected item in select mode. + IconSelect = Styler(FGBold)("▸") +) diff --git a/vendor/github.com/manifoldco/promptui/styles_windows.go b/vendor/github.com/manifoldco/promptui/styles_windows.go new file mode 100644 index 000000000..36de268a6 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/styles_windows.go @@ -0,0 +1,21 @@ +package promptui + +// These are the default icons used bu promptui for select and prompts. They can either be overridden directly +// from these variable or customized through the use of custom templates +var ( + // IconInitial is the icon used when starting in prompt mode and the icon next to the label when + // starting in select mode. + IconInitial = Styler(FGBlue)("?") + + // IconGood is the icon used when a good answer is entered in prompt mode. + IconGood = Styler(FGGreen)("v") + + // IconWarn is the icon used when a good, but potentially invalid answer is entered in prompt mode. + IconWarn = Styler(FGYellow)("!") + + // IconBad is the icon used when a bad answer is entered in prompt mode. + IconBad = Styler(FGRed)("x") + + // IconSelect is the icon used to identify the currently selected item in select mode. + IconSelect = Styler(FGBold)(">") +) diff --git a/vendor/github.com/mattn/go-colorable/.travis.yml b/vendor/github.com/mattn/go-colorable/.travis.yml new file mode 100644 index 000000000..98db8f060 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - tip + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE new file mode 100644 index 000000000..91b5cef30 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md new file mode 100644 index 000000000..56729a92c --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/README.md @@ -0,0 +1,48 @@ +# go-colorable + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable) +[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable) + +Colorable writer for windows. + +For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) +This package is possible to handle escape sequence for ansi color on windows. + +## Too Bad! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) + + +## So Good! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) + +## Usage + +```go +logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) +logrus.SetOutput(colorable.NewColorableStdout()) + +logrus.Info("succeeded") +logrus.Warn("not correct") +logrus.Error("something error") +logrus.Fatal("panic") +``` + +You can compile above code on non-windows OSs. + +## Installation + +``` +$ go get github.com/mattn/go-colorable +``` + +# License + +MIT + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go new file mode 100644 index 000000000..1f28d773d --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go @@ -0,0 +1,29 @@ +// +build appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go new file mode 100644 index 000000000..887f203dc --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_others.go @@ -0,0 +1,30 @@ +// +build !windows +// +build !appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go new file mode 100644 index 000000000..e17a5474e --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -0,0 +1,884 @@ +// +build windows +// +build !appengine + +package colorable + +import ( + "bytes" + "io" + "math" + "os" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/mattn/go-isatty" +) + +const ( + foregroundBlue = 0x1 + foregroundGreen = 0x2 + foregroundRed = 0x4 + foregroundIntensity = 0x8 + foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) + backgroundBlue = 0x10 + backgroundGreen = 0x20 + backgroundRed = 0x40 + backgroundIntensity = 0x80 + backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) +) + +type wchar uint16 +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +type consoleCursorInfo struct { + size dword + visible int32 +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") + procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") + procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") + procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") + procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") +) + +// Writer provide colorable Writer to the console +type Writer struct { + out io.Writer + handle syscall.Handle + oldattr word + oldpos coord +} + +// NewColorable return new instance of Writer which handle escape sequence from File. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + if isatty.IsTerminal(file.Fd()) { + var csbi consoleScreenBufferInfo + handle := syscall.Handle(file.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} + } + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return NewColorable(os.Stdout) +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return NewColorable(os.Stderr) +} + +var color256 = map[int]int{ + 0: 0x000000, + 1: 0x800000, + 2: 0x008000, + 3: 0x808000, + 4: 0x000080, + 5: 0x800080, + 6: 0x008080, + 7: 0xc0c0c0, + 8: 0x808080, + 9: 0xff0000, + 10: 0x00ff00, + 11: 0xffff00, + 12: 0x0000ff, + 13: 0xff00ff, + 14: 0x00ffff, + 15: 0xffffff, + 16: 0x000000, + 17: 0x00005f, + 18: 0x000087, + 19: 0x0000af, + 20: 0x0000d7, + 21: 0x0000ff, + 22: 0x005f00, + 23: 0x005f5f, + 24: 0x005f87, + 25: 0x005faf, + 26: 0x005fd7, + 27: 0x005fff, + 28: 0x008700, + 29: 0x00875f, + 30: 0x008787, + 31: 0x0087af, + 32: 0x0087d7, + 33: 0x0087ff, + 34: 0x00af00, + 35: 0x00af5f, + 36: 0x00af87, + 37: 0x00afaf, + 38: 0x00afd7, + 39: 0x00afff, + 40: 0x00d700, + 41: 0x00d75f, + 42: 0x00d787, + 43: 0x00d7af, + 44: 0x00d7d7, + 45: 0x00d7ff, + 46: 0x00ff00, + 47: 0x00ff5f, + 48: 0x00ff87, + 49: 0x00ffaf, + 50: 0x00ffd7, + 51: 0x00ffff, + 52: 0x5f0000, + 53: 0x5f005f, + 54: 0x5f0087, + 55: 0x5f00af, + 56: 0x5f00d7, + 57: 0x5f00ff, + 58: 0x5f5f00, + 59: 0x5f5f5f, + 60: 0x5f5f87, + 61: 0x5f5faf, + 62: 0x5f5fd7, + 63: 0x5f5fff, + 64: 0x5f8700, + 65: 0x5f875f, + 66: 0x5f8787, + 67: 0x5f87af, + 68: 0x5f87d7, + 69: 0x5f87ff, + 70: 0x5faf00, + 71: 0x5faf5f, + 72: 0x5faf87, + 73: 0x5fafaf, + 74: 0x5fafd7, + 75: 0x5fafff, + 76: 0x5fd700, + 77: 0x5fd75f, + 78: 0x5fd787, + 79: 0x5fd7af, + 80: 0x5fd7d7, + 81: 0x5fd7ff, + 82: 0x5fff00, + 83: 0x5fff5f, + 84: 0x5fff87, + 85: 0x5fffaf, + 86: 0x5fffd7, + 87: 0x5fffff, + 88: 0x870000, + 89: 0x87005f, + 90: 0x870087, + 91: 0x8700af, + 92: 0x8700d7, + 93: 0x8700ff, + 94: 0x875f00, + 95: 0x875f5f, + 96: 0x875f87, + 97: 0x875faf, + 98: 0x875fd7, + 99: 0x875fff, + 100: 0x878700, + 101: 0x87875f, + 102: 0x878787, + 103: 0x8787af, + 104: 0x8787d7, + 105: 0x8787ff, + 106: 0x87af00, + 107: 0x87af5f, + 108: 0x87af87, + 109: 0x87afaf, + 110: 0x87afd7, + 111: 0x87afff, + 112: 0x87d700, + 113: 0x87d75f, + 114: 0x87d787, + 115: 0x87d7af, + 116: 0x87d7d7, + 117: 0x87d7ff, + 118: 0x87ff00, + 119: 0x87ff5f, + 120: 0x87ff87, + 121: 0x87ffaf, + 122: 0x87ffd7, + 123: 0x87ffff, + 124: 0xaf0000, + 125: 0xaf005f, + 126: 0xaf0087, + 127: 0xaf00af, + 128: 0xaf00d7, + 129: 0xaf00ff, + 130: 0xaf5f00, + 131: 0xaf5f5f, + 132: 0xaf5f87, + 133: 0xaf5faf, + 134: 0xaf5fd7, + 135: 0xaf5fff, + 136: 0xaf8700, + 137: 0xaf875f, + 138: 0xaf8787, + 139: 0xaf87af, + 140: 0xaf87d7, + 141: 0xaf87ff, + 142: 0xafaf00, + 143: 0xafaf5f, + 144: 0xafaf87, + 145: 0xafafaf, + 146: 0xafafd7, + 147: 0xafafff, + 148: 0xafd700, + 149: 0xafd75f, + 150: 0xafd787, + 151: 0xafd7af, + 152: 0xafd7d7, + 153: 0xafd7ff, + 154: 0xafff00, + 155: 0xafff5f, + 156: 0xafff87, + 157: 0xafffaf, + 158: 0xafffd7, + 159: 0xafffff, + 160: 0xd70000, + 161: 0xd7005f, + 162: 0xd70087, + 163: 0xd700af, + 164: 0xd700d7, + 165: 0xd700ff, + 166: 0xd75f00, + 167: 0xd75f5f, + 168: 0xd75f87, + 169: 0xd75faf, + 170: 0xd75fd7, + 171: 0xd75fff, + 172: 0xd78700, + 173: 0xd7875f, + 174: 0xd78787, + 175: 0xd787af, + 176: 0xd787d7, + 177: 0xd787ff, + 178: 0xd7af00, + 179: 0xd7af5f, + 180: 0xd7af87, + 181: 0xd7afaf, + 182: 0xd7afd7, + 183: 0xd7afff, + 184: 0xd7d700, + 185: 0xd7d75f, + 186: 0xd7d787, + 187: 0xd7d7af, + 188: 0xd7d7d7, + 189: 0xd7d7ff, + 190: 0xd7ff00, + 191: 0xd7ff5f, + 192: 0xd7ff87, + 193: 0xd7ffaf, + 194: 0xd7ffd7, + 195: 0xd7ffff, + 196: 0xff0000, + 197: 0xff005f, + 198: 0xff0087, + 199: 0xff00af, + 200: 0xff00d7, + 201: 0xff00ff, + 202: 0xff5f00, + 203: 0xff5f5f, + 204: 0xff5f87, + 205: 0xff5faf, + 206: 0xff5fd7, + 207: 0xff5fff, + 208: 0xff8700, + 209: 0xff875f, + 210: 0xff8787, + 211: 0xff87af, + 212: 0xff87d7, + 213: 0xff87ff, + 214: 0xffaf00, + 215: 0xffaf5f, + 216: 0xffaf87, + 217: 0xffafaf, + 218: 0xffafd7, + 219: 0xffafff, + 220: 0xffd700, + 221: 0xffd75f, + 222: 0xffd787, + 223: 0xffd7af, + 224: 0xffd7d7, + 225: 0xffd7ff, + 226: 0xffff00, + 227: 0xffff5f, + 228: 0xffff87, + 229: 0xffffaf, + 230: 0xffffd7, + 231: 0xffffff, + 232: 0x080808, + 233: 0x121212, + 234: 0x1c1c1c, + 235: 0x262626, + 236: 0x303030, + 237: 0x3a3a3a, + 238: 0x444444, + 239: 0x4e4e4e, + 240: 0x585858, + 241: 0x626262, + 242: 0x6c6c6c, + 243: 0x767676, + 244: 0x808080, + 245: 0x8a8a8a, + 246: 0x949494, + 247: 0x9e9e9e, + 248: 0xa8a8a8, + 249: 0xb2b2b2, + 250: 0xbcbcbc, + 251: 0xc6c6c6, + 252: 0xd0d0d0, + 253: 0xdadada, + 254: 0xe4e4e4, + 255: 0xeeeeee, +} + +// `\033]0;TITLESTR\007` +func doTitleSequence(er *bytes.Reader) error { + var c byte + var err error + + c, err = er.ReadByte() + if err != nil { + return err + } + if c != '0' && c != '2' { + return nil + } + c, err = er.ReadByte() + if err != nil { + return err + } + if c != ';' { + return nil + } + title := make([]byte, 0, 80) + for { + c, err = er.ReadByte() + if err != nil { + return err + } + if c == 0x07 || c == '\n' { + break + } + title = append(title, c) + } + if len(title) > 0 { + title8, err := syscall.UTF16PtrFromString(string(title)) + if err == nil { + procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) + } + } + return nil +} + +// Write write data on console +func (w *Writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + + if c2 == ']' { + if err := doTitleSequence(er); err != nil { + break loop + } + continue + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + var m byte + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + break + } + buf.Write([]byte(string(c))) + } + + switch m { + case 'A': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'B': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'C': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'D': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'E': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'F': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'G': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = short(n - 1) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'H', 'f': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + if buf.Len() > 0 { + token := strings.Split(buf.String(), ";") + switch len(token) { + case 1: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + csbi.cursorPosition.y = short(n1 - 1) + case 2: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + n2, err := strconv.Atoi(token[1]) + if err != nil { + continue + } + csbi.cursorPosition.x = short(n2 - 1) + csbi.cursorPosition.y = short(n1 - 1) + } + } else { + csbi.cursorPosition.y = 0 + } + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'J': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + var count, written dword + var cursor coord + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'K': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + var count, written dword + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x + 1, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x - 1) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + count = dword(csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'm': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) + continue + } + token := strings.Split(cs, ";") + for i := 0; i < len(token); i++ { + ns := token[i] + if n, err = strconv.Atoi(ns); err == nil { + switch { + case n == 0 || n == 100: + attr = w.oldattr + case 1 <= n && n <= 5: + attr |= foregroundIntensity + case n == 7: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case n == 22 || n == 25: + attr |= foregroundIntensity + case n == 27: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 30 <= n && n <= 37: + attr &= backgroundMask + if (n-30)&1 != 0 { + attr |= foregroundRed + } + if (n-30)&2 != 0 { + attr |= foregroundGreen + } + if (n-30)&4 != 0 { + attr |= foregroundBlue + } + case n == 38: // set foreground color. + if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256foreAttr == nil { + n256setup() + } + attr &= backgroundMask + attr |= n256foreAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & backgroundMask) + } + case n == 39: // reset foreground color. + attr &= backgroundMask + attr |= w.oldattr & foregroundMask + case 40 <= n && n <= 47: + attr &= foregroundMask + if (n-40)&1 != 0 { + attr |= backgroundRed + } + if (n-40)&2 != 0 { + attr |= backgroundGreen + } + if (n-40)&4 != 0 { + attr |= backgroundBlue + } + case n == 48: // set background color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256backAttr == nil { + n256setup() + } + attr &= foregroundMask + attr |= n256backAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & foregroundMask) + } + case n == 49: // reset foreground color. + attr &= foregroundMask + attr |= w.oldattr & backgroundMask + case 90 <= n && n <= 97: + attr = (attr & backgroundMask) + attr |= foregroundIntensity + if (n-90)&1 != 0 { + attr |= foregroundRed + } + if (n-90)&2 != 0 { + attr |= foregroundGreen + } + if (n-90)&4 != 0 { + attr |= foregroundBlue + } + case 100 <= n && n <= 107: + attr = (attr & foregroundMask) + attr |= backgroundIntensity + if (n-100)&1 != 0 { + attr |= backgroundRed + } + if (n-100)&2 != 0 { + attr |= backgroundGreen + } + if (n-100)&4 != 0 { + attr |= backgroundBlue + } + } + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) + } + } + case 'h': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 'l': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 's': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + w.oldpos = csbi.cursorPosition + case 'u': + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) + } + } + + return len(data), nil +} + +type consoleColor struct { + rgb int + red bool + green bool + blue bool + intensity bool +} + +func (c consoleColor) foregroundAttr() (attr word) { + if c.red { + attr |= foregroundRed + } + if c.green { + attr |= foregroundGreen + } + if c.blue { + attr |= foregroundBlue + } + if c.intensity { + attr |= foregroundIntensity + } + return +} + +func (c consoleColor) backgroundAttr() (attr word) { + if c.red { + attr |= backgroundRed + } + if c.green { + attr |= backgroundGreen + } + if c.blue { + attr |= backgroundBlue + } + if c.intensity { + attr |= backgroundIntensity + } + return +} + +var color16 = []consoleColor{ + {0x000000, false, false, false, false}, + {0x000080, false, false, true, false}, + {0x008000, false, true, false, false}, + {0x008080, false, true, true, false}, + {0x800000, true, false, false, false}, + {0x800080, true, false, true, false}, + {0x808000, true, true, false, false}, + {0xc0c0c0, true, true, true, false}, + {0x808080, false, false, false, true}, + {0x0000ff, false, false, true, true}, + {0x00ff00, false, true, false, true}, + {0x00ffff, false, true, true, true}, + {0xff0000, true, false, false, true}, + {0xff00ff, true, false, true, true}, + {0xffff00, true, true, false, true}, + {0xffffff, true, true, true, true}, +} + +type hsv struct { + h, s, v float32 +} + +func (a hsv) dist(b hsv) float32 { + dh := a.h - b.h + switch { + case dh > 0.5: + dh = 1 - dh + case dh < -0.5: + dh = -1 - dh + } + ds := a.s - b.s + dv := a.v - b.v + return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) +} + +func toHSV(rgb int) hsv { + r, g, b := float32((rgb&0xFF0000)>>16)/256.0, + float32((rgb&0x00FF00)>>8)/256.0, + float32(rgb&0x0000FF)/256.0 + min, max := minmax3f(r, g, b) + h := max - min + if h > 0 { + if max == r { + h = (g - b) / h + if h < 0 { + h += 6 + } + } else if max == g { + h = 2 + (b-r)/h + } else { + h = 4 + (r-g)/h + } + } + h /= 6.0 + s := max - min + if max != 0 { + s /= max + } + v := max + return hsv{h: h, s: s, v: v} +} + +type hsvTable []hsv + +func toHSVTable(rgbTable []consoleColor) hsvTable { + t := make(hsvTable, len(rgbTable)) + for i, c := range rgbTable { + t[i] = toHSV(c.rgb) + } + return t +} + +func (t hsvTable) find(rgb int) consoleColor { + hsv := toHSV(rgb) + n := 7 + l := float32(5.0) + for i, p := range t { + d := hsv.dist(p) + if d < l { + l, n = d, i + } + } + return color16[n] +} + +func minmax3f(a, b, c float32) (min, max float32) { + if a < b { + if b < c { + return a, c + } else if a < c { + return a, b + } else { + return c, b + } + } else { + if a < c { + return b, c + } else if b < c { + return b, a + } else { + return c, a + } + } +} + +var n256foreAttr []word +var n256backAttr []word + +func n256setup() { + n256foreAttr = make([]word, 256) + n256backAttr = make([]word, 256) + t := toHSVTable(color16) + for i, rgb := range color256 { + c := t.find(rgb) + n256foreAttr[i] = c.foregroundAttr() + n256backAttr[i] = c.backgroundAttr() + } +} diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go new file mode 100644 index 000000000..9721e16f4 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -0,0 +1,55 @@ +package colorable + +import ( + "bytes" + "io" +) + +// NonColorable hold writer but remove escape sequence. +type NonColorable struct { + out io.Writer +} + +// NewNonColorable return new instance of Writer which remove escape sequence from Writer. +func NewNonColorable(w io.Writer) io.Writer { + return &NonColorable{out: w} +} + +// Write write data on console +func (w *NonColorable) Write(data []byte) (n int, err error) { + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + break + } + buf.Write([]byte(string(c))) + } + } + + return len(data), nil +} diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml new file mode 100644 index 000000000..5597e026d --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - tip + +os: + - linux + - osx + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE new file mode 100644 index 000000000..65dc692b6 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com> + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md new file mode 100644 index 000000000..1e69004bb --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -0,0 +1,50 @@ +# go-isatty + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) +[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) + +isatty for golang + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/mattn/go-isatty" + "os" +) + +func main() { + if isatty.IsTerminal(os.Stdout.Fd()) { + fmt.Println("Is Terminal") + } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { + fmt.Println("Is Cygwin/MSYS2 Terminal") + } else { + fmt.Println("Is Not Terminal") + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-isatty +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) + +## Thanks + +* k-takata: base idea for IsCygwinTerminal + + https://github.com/k-takata/go-iscygpty diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go new file mode 100644 index 000000000..17d4f90eb --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/doc.go @@ -0,0 +1,2 @@ +// Package isatty implements interface to isatty +package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/mattn/go-isatty/isatty_appengine.go new file mode 100644 index 000000000..9584a9884 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_appengine.go @@ -0,0 +1,15 @@ +// +build appengine + +package isatty + +// IsTerminal returns true if the file descriptor is terminal which +// is always false on on appengine classic which is a sandboxed PaaS. +func IsTerminal(fd uintptr) bool { + return false +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go new file mode 100644 index 000000000..42f2514d1 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -0,0 +1,18 @@ +// +build darwin freebsd openbsd netbsd dragonfly +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TIOCGETA + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/mattn/go-isatty/isatty_linux.go new file mode 100644 index 000000000..7384cf991 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux.go @@ -0,0 +1,18 @@ +// +build linux +// +build !appengine,!ppc64,!ppc64le + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go new file mode 100644 index 000000000..44e5d2130 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go @@ -0,0 +1,19 @@ +// +build linux +// +build ppc64 ppc64le + +package isatty + +import ( + "unsafe" + + syscall "golang.org/x/sys/unix" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go new file mode 100644 index 000000000..9d8b4a599 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -0,0 +1,10 @@ +// +build !windows +// +build !appengine + +package isatty + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go new file mode 100644 index 000000000..1f0c6bf53 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -0,0 +1,16 @@ +// +build solaris +// +build !appengine + +package isatty + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func IsTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go new file mode 100644 index 000000000..af51cbcaa --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -0,0 +1,94 @@ +// +build windows +// +build !appengine + +package isatty + +import ( + "strings" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + fileNameInfo uintptr = 2 + fileTypePipe = 3 +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") + procGetFileType = kernel32.NewProc("GetFileType") +) + +func init() { + // Check if GetFileInformationByHandleEx is available. + if procGetFileInformationByHandleEx.Find() != nil { + procGetFileInformationByHandleEx = nil + } +} + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// Check pipe name is used for cygwin/msys2 pty. +// Cygwin/MSYS2 PTY has a name like: +// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master +func isCygwinPipeName(name string) bool { + token := strings.Split(name, "-") + if len(token) < 5 { + return false + } + + if token[0] != `\msys` && token[0] != `\cygwin` { + return false + } + + if token[1] == "" { + return false + } + + if !strings.HasPrefix(token[2], "pty") { + return false + } + + if token[3] != `from` && token[3] != `to` { + return false + } + + if token[4] != "master" { + return false + } + + return true +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. +func IsCygwinTerminal(fd uintptr) bool { + if procGetFileInformationByHandleEx == nil { + return false + } + + // Cygwin/msys's pty is a pipe. + ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) + if ft != fileTypePipe || e != 0 { + return false + } + + var buf [2 + syscall.MAX_PATH]uint16 + r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), + 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), + uintptr(len(buf)*2), 0, 0) + if r == 0 || e != 0 { + return false + } + + l := *(*uint32)(unsafe.Pointer(&buf)) + return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) +} diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go index 846636d75..2ceebc552 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.go +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -5,70 +5,95 @@ import ( "fmt" "io" "os" - "sort" - "strings" - - "github.com/spf13/pflag" ) // Annotations for Bash completion. const ( - BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" + BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" + // BashCompCustom should be avoided as it only works for bash. + // Function RegisterFlagCompletionFunc() should be used instead. BashCompCustom = "cobra_annotation_bash_completion_custom" BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" ) -func writePreamble(buf *bytes.Buffer, name string) { - buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) - buf.WriteString(fmt.Sprintf(` -__%[1]s_debug() -{ - if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then - echo "$*" >> "${BASH_COMP_DEBUG_FILE}" - fi +// GenBashCompletion generates bash completion file and writes to the passed writer. +func (c *Command) GenBashCompletion(w io.Writer) error { + return c.genBashCompletion(w, false) } -# Homebrew on Macs have version 1.3 of bash-completion which doesn't include -# _init_completion. This is a very minimal version of that function. -__%[1]s_init_completion() -{ - COMPREPLY=() - _get_comp_words_by_ref "$@" cur prev words cword +// GenBashCompletionWithDesc generates bash completion file with descriptions and writes to the passed writer. +func (c *Command) GenBashCompletionWithDesc(w io.Writer) error { + return c.genBashCompletion(w, true) } -__%[1]s_index_of_word() -{ - local w word=$1 - shift - index=0 - for w in "$@"; do - [[ $w = "$word" ]] && return - index=$((index+1)) - done - index=-1 +// GenBashCompletionFile generates bash completion file. +func (c *Command) GenBashCompletionFile(filename string) error { + return c.genBashCompletionFile(filename, false) +} + +// GenBashCompletionFileWithDesc generates bash completion file with descriptions. +func (c *Command) GenBashCompletionFileWithDesc(filename string) error { + return c.genBashCompletionFile(filename, true) +} + +func (c *Command) genBashCompletionFile(filename string, includeDesc bool) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.genBashCompletion(outFile, includeDesc) } -__%[1]s_contains_word() +func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { + buf := new(bytes.Buffer) + if len(c.BashCompletionFunction) > 0 { + buf.WriteString(c.BashCompletionFunction + "\n") + } + genBashComp(buf, c.Name(), includeDesc) + + _, err := buf.WriteTo(w) + return err +} + +func genBashComp(buf *bytes.Buffer, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd + } + + buf.WriteString(fmt.Sprintf(`# bash completion for %-36[1]s -*- shell-script -*- + +__%[1]s_debug() { - local w word=$1; shift - for w in "$@"; do - [[ $w = "$word" ]] && return - done - return 1 + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi } -__%[1]s_handle_go_custom_completion() +__%[1]s_perform_completion() { - __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + __%[1]s_debug + __%[1]s_debug "========= starting completion logic ==========" + __%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $cword location, so we need + # to truncate the command-line ($words) up to the $cword location. + words=("${words[@]:0:$cword+1}") + __%[1]s_debug "Truncated words[*]: ${words[*]}," local shellCompDirectiveError=%[3]d local shellCompDirectiveNoSpace=%[4]d local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d + local shellCompDirectiveLegacyCustomComp=%[8]d + local shellCompDirectiveLegacyCustomArgsComp=%[9]d - local out requestComp lastParam lastChar comp directive args + local out requestComp lastParam lastChar comp directive args flagPrefix # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases @@ -77,16 +102,24 @@ __%[1]s_handle_go_custom_completion() lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} - __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}" if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then # If the last parameter is complete (there is a space following it) # We add an extra empty parameter so we can indicate this to the go method. - __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" + __%[1]s_debug "Adding extra empty parameter" requestComp="${requestComp} \"\"" fi - __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" + # When completing a flag with an = (e.g., %[1]s -n=<TAB>) + # bash focuses on the part after the =, so we need to remove + # the flag part from $cur + if [[ "${cur}" == -*=* ]]; then + flagPrefix="${cur%%%%=*}=" + cur="${cur#*=}" + fi + + __%[1]s_debug "Calling ${requestComp}" # Use eval to handle any environment variables and such out=$(eval "${requestComp}" 2>/dev/null) @@ -98,23 +131,23 @@ __%[1]s_handle_go_custom_completion() # There is not directive specified directive=0 fi - __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" - __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" + __%[1]s_debug "The completion directive is: ${directive}" + __%[1]s_debug "The completions are: ${out[*]}" if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. - __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" + __%[1]s_debug "Received error from custom completion go code" return else if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then - __%[1]s_debug "${FUNCNAME[0]}: activating no space" + __%[1]s_debug "Activating no space" compopt -o nospace fi fi if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then - __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" + __%[1]s_debug "Activating no file completion" compopt +o default fi fi @@ -123,6 +156,7 @@ __%[1]s_handle_go_custom_completion() if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline # characters will be kept. for filter in ${out[*]}; do @@ -134,545 +168,173 @@ __%[1]s_handle_go_custom_completion() $filteringCmd elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then # File completion for directories only - local subDir + # Use printf to strip any trailing newline + local subdir subdir=$(printf "%%s" "${out[0]}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" - __%[1]s_handle_subdirs_in_dir_flag "$subdir" + pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return else __%[1]s_debug "Listing directories in ." _filedir -d fi + elif [ $((directive & shellCompDirectiveLegacyCustomComp)) -ne 0 ]; then + local cmd + __%[1]s_debug "Legacy custom completion. Directive: $directive, cmds: ${out[*]}" + + # The following variables should get their value through the commands + # we have received as completions and are parsing below. + local last_command + local nouns + + # Execute every command received + while IFS='' read -r cmd; do + __%[1]s_debug "About to execute: $cmd" + eval "$cmd" + done < <(printf "%%s\n" "${out[@]}") + + __%[1]s_debug "last_command: $last_command" + __%[1]s_debug "nouns[0]: ${nouns[0]}, nouns[1]: ${nouns[1]}" + + if [ $((directive & shellCompDirectiveLegacyCustomArgsComp)) -ne 0 ]; then + # We should call the global legacy custom completion function, if it is defined + if declare -F __%[1]s_custom_func >/dev/null; then + # Use command name qualified legacy custom func + __%[1]s_debug "About to call: __%[1]s_custom_func" + __%[1]s_custom_func + elif declare -F __custom_func >/dev/null; then + # Otherwise fall back to unqualified legacy custom func for compatibility + __%[1]s_debug "About to call: __custom_func" + __custom_func + fi + fi else + local tab + tab=$(printf '\t') + local longest=0 + # Look for the longest completion so that we can format things nicely while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${out[*]}" -- "$cur") - fi -} - -__%[1]s_handle_reply() -{ - __%[1]s_debug "${FUNCNAME[0]}" - local comp - case $cur in - -*) - if [[ $(type -t compopt) = "builtin" ]]; then - compopt -o nospace - fi - local allflags - if [ ${#must_have_one_flag[@]} -ne 0 ]; then - allflags=("${must_have_one_flag[@]}") - else - allflags=("${flags[*]} ${two_word_flags[*]}") - fi - while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${allflags[*]}" -- "$cur") - if [[ $(type -t compopt) = "builtin" ]]; then - [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + comp=${comp%%%%$tab*} + if ((${#comp}>longest)); then + longest=${#comp} fi + done < <(printf "%%s\n" "${out[@]}") - # complete after --flag=abc - if [[ $cur == *=* ]]; then - if [[ $(type -t compopt) = "builtin" ]]; then - compopt +o nospace - fi - - local index flag - flag="${cur%%=*}" - __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}" - COMPREPLY=() - if [[ ${index} -ge 0 ]]; then - PREFIX="" - cur="${cur#*=}" - ${flags_completion[${index}]} - if [ -n "${ZSH_VERSION}" ]; then - # zsh completion needs --flag= prefix - eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" - fi - fi + local completions=() + while IFS='' read -r comp; do + if [ -z "$comp" ]; then + continue fi - return 0; - ;; - esac - - # check if we are handling a flag with special work handling - local index - __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}" - if [[ ${index} -ge 0 ]]; then - ${flags_completion[${index}]} - return - fi - # we are parsing a flag and don't have a special handler, no completion - if [[ ${cur} != "${words[cword]}" ]]; then - return - fi + __%[1]s_debug "Original comp: $comp" + comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")" + __%[1]s_debug "Final comp: $comp" + completions+=("$comp") + done < <(printf "%%s\n" "${out[@]}") - local completions - completions=("${commands[@]}") - if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then - completions+=("${must_have_one_noun[@]}") - elif [[ -n "${has_completion_function}" ]]; then - # if a go completion function is provided, defer to that function - __%[1]s_handle_go_custom_completion - fi - if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then - completions+=("${must_have_one_flag[@]}") - fi - while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${completions[*]}" -- "$cur") - - if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then while IFS='' read -r comp; do + # Although this script should only be used for bash + # there may be programs that still convert the bash + # script into a zsh one. To continue supporting those + # programs, we do this single adaptation for zsh + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + COMPREPLY+=("$flagPrefix$comp") + else + COMPREPLY+=("$comp") + fi + done < <(compgen -W "${completions[*]}" -- "$cur") + + # If there is a single completion left, remove the description text + if [ ${#COMPREPLY[*]} -eq 1 ]; then + __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" + comp="${COMPREPLY[0]%%%% *}" + __%[1]s_debug "Removed description from single completion, which is now: ${comp}" + COMPREPLY=() COMPREPLY+=("$comp") - done < <(compgen -W "${noun_aliases[*]}" -- "$cur") - fi - - if [[ ${#COMPREPLY[@]} -eq 0 ]]; then - if declare -F __%[1]s_custom_func >/dev/null; then - # try command name qualified custom func - __%[1]s_custom_func - else - # otherwise fall back to unqualified for compatibility - declare -F __custom_func >/dev/null && __custom_func - fi - fi - - # available in bash-completion >= 2, not always present on macOS - if declare -F __ltrim_colon_completions >/dev/null; then - __ltrim_colon_completions "$cur" - fi - - # If there is only 1 completion and it is a flag with an = it will be completed - # but we don't want a space after the = - if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then - compopt -o nospace - fi -} - -# The arguments should be in the form "ext1|ext2|extn" -__%[1]s_handle_filename_extension_flag() -{ - local ext="$1" - _filedir "@(${ext})" -} - -__%[1]s_handle_subdirs_in_dir_flag() -{ - local dir="$1" - pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return -} - -__%[1]s_handle_flag() -{ - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - # if a command required a flag, and we found it, unset must_have_one_flag() - local flagname=${words[c]} - local flagvalue - # if the word contained an = - if [[ ${words[c]} == *"="* ]]; then - flagvalue=${flagname#*=} # take in as flagvalue after the = - flagname=${flagname%%=*} # strip everything after the = - flagname="${flagname}=" # but put the = back - fi - __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}" - if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then - must_have_one_flag=() - fi - - # if you set a flag which only applies to this command, don't show subcommands - if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then - commands=() - fi - - # keep flag value with flagname as flaghash - # flaghash variable is an associative array which is only supported in bash > 3. - if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then - if [ -n "${flagvalue}" ] ; then - flaghash[${flagname}]=${flagvalue} - elif [ -n "${words[ $((c+1)) ]}" ] ; then - flaghash[${flagname}]=${words[ $((c+1)) ]} - else - flaghash[${flagname}]="true" # pad "true" for bool flag - fi - fi - - # skip the argument to a two word flag - if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then - __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" - c=$((c+1)) - # if we are looking for a flags value, don't show commands - if [[ $c -eq $cword ]]; then - commands=() fi fi - c=$((c+1)) - + __%[1]s_handle_special_char "$cur" : + __%[1]s_handle_special_char "$cur" = } -__%[1]s_handle_noun() +__%[1]s_handle_special_char() { - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then - must_have_one_noun=() - elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then - must_have_one_noun=() + local comp="$1" + local char=$2 + if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then + local word=${comp%%"${comp##*${char}}"} + local idx=${#COMPREPLY[*]} + while [[ $((--idx)) -ge 0 ]]; do + COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"} + done fi - - nouns+=("${words[c]}") - c=$((c+1)) } -__%[1]s_handle_command() +__%[1]s_format_comp_descriptions() { - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - local next_command - if [[ -n ${last_command} ]]; then - next_command="_${last_command}_${words[c]//:/__}" - else - if [[ $c -eq 0 ]]; then - next_command="_%[1]s_root_command" + local tab + tab=$(printf '\t') + local comp="$1" + local longest=$2 + + # Properly format the description string which follows a tab character if there is one + if [[ "$comp" == *$tab* ]]; then + desc=${comp#*$tab} + comp=${comp%%%%$tab*} + + # $COLUMNS stores the current shell width. + # Remove an extra 4 because we add 2 spaces and 2 parentheses. + maxdesclength=$(( COLUMNS - longest - 4 )) + + # Make sure we can fit a description of at least 8 characters + # if we are to align the descriptions. + if [[ $maxdesclength -gt 8 ]]; then + # Add the proper number of spaces to align the descriptions + for ((i = ${#comp} ; i < longest ; i++)); do + comp+=" " + done else - next_command="_${words[c]//:/__}" + # Don't pad the descriptions so we can fit more text after the completion + maxdesclength=$(( COLUMNS - ${#comp} - 4 )) fi - fi - c=$((c+1)) - __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}" - declare -F "$next_command" >/dev/null && $next_command -} -__%[1]s_handle_word() -{ - if [[ $c -ge $cword ]]; then - __%[1]s_handle_reply - return - fi - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - if [[ "${words[c]}" == -* ]]; then - __%[1]s_handle_flag - elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then - __%[1]s_handle_command - elif [[ $c -eq 0 ]]; then - __%[1]s_handle_command - elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then - # aliashash variable is an associative array which is only supported in bash > 3. - if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then - words[c]=${aliashash[${words[c]}]} - __%[1]s_handle_command - else - __%[1]s_handle_noun + # If there is enough space for any description text, + # truncate the descriptions that are too long for the shell width + if [ $maxdesclength -gt 0 ]; then + if [ ${#desc} -gt $maxdesclength ]; then + desc=${desc:0:$(( maxdesclength - 1 ))} + desc+="…" + fi + comp+=" ($desc)" fi - else - __%[1]s_handle_noun fi - __%[1]s_handle_word -} -`, name, ShellCompNoDescRequestCmd, - ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + # Must use printf to escape all special characters + printf "%%q" "${comp}" } -func writePostscript(buf *bytes.Buffer, name string) { - name = strings.Replace(name, ":", "__", -1) - buf.WriteString(fmt.Sprintf("__start_%s()\n", name)) - buf.WriteString(fmt.Sprintf(`{ +__start_%[1]s() +{ local cur prev words cword - declare -A flaghash 2>/dev/null || : - declare -A aliashash 2>/dev/null || : - if declare -F _init_completion >/dev/null 2>&1; then - _init_completion -s || return - else - __%[1]s_init_completion -n "=" || return - fi - local c=0 - local flags=() - local two_word_flags=() - local local_nonpersistent_flags=() - local flags_with_completion=() - local flags_completion=() - local commands=("%[1]s") - local must_have_one_flag=() - local must_have_one_noun=() - local has_completion_function - local last_command - local nouns=() - - __%[1]s_handle_word + COMPREPLY=() + _get_comp_words_by_ref -n "=:" cur prev words cword + + __%[1]s_perform_completion } -`, name)) - buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then - complete -o default -F __start_%s %s +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_%[1]s %[1]s else - complete -o default -o nospace -F __start_%s %s + complete -o default -o nospace -F __start_%[1]s %[1]s fi -`, name, name, name, name)) - buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n") -} - -func writeCommands(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" commands=()\n") - for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() && c != cmd.helpCommand { - continue - } - buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) - writeCmdAliases(buf, c) - } - buf.WriteString("\n") -} - -func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) { - for key, value := range annotations { - switch key { - case BashCompFilenameExt: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) - - var ext string - if len(value) > 0 { - ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|") - } else { - ext = "_filedir" - } - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) - case BashCompCustom: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) - if len(value) > 0 { - handlers := strings.Join(value, "; ") - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) - } else { - buf.WriteString(" flags_completion+=(:)\n") - } - case BashCompSubdirsInDir: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) - - var ext string - if len(value) == 1 { - ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0] - } else { - ext = "_filedir -d" - } - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) - } - } -} - -func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { - name := flag.Shorthand - format := " " - if len(flag.NoOptDefVal) == 0 { - format += "two_word_" - } - format += "flags+=(\"-%s\")\n" - buf.WriteString(fmt.Sprintf(format, name)) - writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) -} - -func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { - name := flag.Name - format := " flags+=(\"--%s" - if len(flag.NoOptDefVal) == 0 { - format += "=" - } - format += "\")\n" - buf.WriteString(fmt.Sprintf(format, name)) - if len(flag.NoOptDefVal) == 0 { - format = " two_word_flags+=(\"--%s\")\n" - buf.WriteString(fmt.Sprintf(format, name)) - } - writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) -} - -func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { - name := flag.Name - format := " local_nonpersistent_flags+=(\"--%[1]s\")\n" - if len(flag.NoOptDefVal) == 0 { - format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n" - } - buf.WriteString(fmt.Sprintf(format, name)) - if len(flag.Shorthand) > 0 { - buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand)) - } -} - -// Setup annotations for go completions for registered flags -func prepareCustomAnnotationsForFlags(cmd *Command) { - for flag := range flagCompletionFunctions { - // Make sure the completion script calls the __*_go_custom_completion function for - // every registered flag. We need to do this here (and not when the flag was registered - // for completion) so that we can know the root command name for the prefix - // of __<prefix>_go_custom_completion - if flag.Annotations == nil { - flag.Annotations = map[string][]string{} - } - flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())} - } -} - -func writeFlags(buf *bytes.Buffer, cmd *Command) { - prepareCustomAnnotationsForFlags(cmd) - buf.WriteString(` flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - -`) - localNonPersistentFlags := cmd.LocalNonPersistentFlags() - cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - writeFlag(buf, flag, cmd) - if len(flag.Shorthand) > 0 { - writeShortFlag(buf, flag, cmd) - } - // localNonPersistentFlags are used to stop the completion of subcommands when one is set - // if TraverseChildren is true we should allow to complete subcommands - if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren { - writeLocalNonPersistentFlag(buf, flag) - } - }) - cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - writeFlag(buf, flag, cmd) - if len(flag.Shorthand) > 0 { - writeShortFlag(buf, flag, cmd) - } - }) - - buf.WriteString("\n") -} - -func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" must_have_one_flag=()\n") - flags := cmd.NonInheritedFlags() - flags.VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - for key := range flag.Annotations { - switch key { - case BashCompOneRequiredFlag: - format := " must_have_one_flag+=(\"--%s" - if flag.Value.Type() != "bool" { - format += "=" - } - format += "\")\n" - buf.WriteString(fmt.Sprintf(format, flag.Name)) - - if len(flag.Shorthand) > 0 { - buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)) - } - } - } - }) -} - -func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" must_have_one_noun=()\n") - sort.Sort(sort.StringSlice(cmd.ValidArgs)) - for _, value := range cmd.ValidArgs { - // Remove any description that may be included following a tab character. - // Descriptions are not supported by bash completion. - value = strings.Split(value, "\t")[0] - buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) - } - if cmd.ValidArgsFunction != nil { - buf.WriteString(" has_completion_function=1\n") - } -} - -func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { - if len(cmd.Aliases) == 0 { - return - } - - sort.Sort(sort.StringSlice(cmd.Aliases)) - - buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) - for _, value := range cmd.Aliases { - buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) - buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) - } - buf.WriteString(` fi`) - buf.WriteString("\n") -} -func writeArgAliases(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" noun_aliases=()\n") - sort.Sort(sort.StringSlice(cmd.ArgAliases)) - for _, value := range cmd.ArgAliases { - buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value)) - } -} - -func gen(buf *bytes.Buffer, cmd *Command) { - for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() && c != cmd.helpCommand { - continue - } - gen(buf, c) - } - commandName := cmd.CommandPath() - commandName = strings.Replace(commandName, " ", "_", -1) - commandName = strings.Replace(commandName, ":", "__", -1) - - if cmd.Root() == cmd { - buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName)) - } else { - buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) - } - - buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) - buf.WriteString("\n") - buf.WriteString(" command_aliases=()\n") - buf.WriteString("\n") - - writeCommands(buf, cmd) - writeFlags(buf, cmd) - writeRequiredFlag(buf, cmd) - writeRequiredNouns(buf, cmd) - writeArgAliases(buf, cmd) - buf.WriteString("}\n\n") -} - -// GenBashCompletion generates bash completion file and writes to the passed writer. -func (c *Command) GenBashCompletion(w io.Writer) error { - buf := new(bytes.Buffer) - writePreamble(buf, c.Name()) - if len(c.BashCompletionFunction) > 0 { - buf.WriteString(c.BashCompletionFunction + "\n") - } - gen(buf, c) - writePostscript(buf, c.Name()) - - _, err := buf.WriteTo(w) - return err -} - -func nonCompletableFlag(flag *pflag.Flag) bool { - return flag.Hidden || len(flag.Deprecated) > 0 -} - -// GenBashCompletionFile generates bash completion file. -func (c *Command) GenBashCompletionFile(filename string) error { - outFile, err := os.Create(filename) - if err != nil { - return err - } - defer outFile.Close() - - return c.GenBashCompletion(outFile) +# ex: ts=4 sw=4 et filetype=sh +`, name, compCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) } diff --git a/vendor/github.com/spf13/cobra/custom_completions.go b/vendor/github.com/spf13/cobra/custom_completions.go index f9e88e081..e2c68ae1e 100644 --- a/vendor/github.com/spf13/cobra/custom_completions.go +++ b/vendor/github.com/spf13/cobra/custom_completions.go @@ -51,6 +51,11 @@ const ( // obtain the same behavior but only for flags. ShellCompDirectiveFilterDirs + // For internal use only. + // Used to maintain backwards-compatibility with the legacy bash custom completions. + shellCompDirectiveLegacyCustomComp + shellCompDirectiveLegacyCustomArgsComp + // =========================================================================== // All directives using iota should be above this one. @@ -94,6 +99,12 @@ func (d ShellCompDirective) string() string { if d&ShellCompDirectiveFilterDirs != 0 { directives = append(directives, "ShellCompDirectiveFilterDirs") } + if d&shellCompDirectiveLegacyCustomComp != 0 { + directives = append(directives, "shellCompDirectiveLegacyCustomComp") + } + if d&shellCompDirectiveLegacyCustomArgsComp != 0 { + directives = append(directives, "shellCompDirectiveLegacyCustomArgsComp") + } if len(directives) == 0 { directives = append(directives, "ShellCompDirectiveDefault") } @@ -149,10 +160,6 @@ func (c *Command) initCompleteCmd(args []string) { fmt.Fprintln(finalCmd.OutOrStdout(), comp) } - if directive >= shellCompDirectiveMaxValue { - directive = ShellCompDirectiveDefault - } - // As the last printout, print the completion directive for the completion script to parse. // The directive integer must be that last character following a single colon (:). // The completion script expects :<directive> @@ -362,6 +369,10 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi var comps []string comps, directive = completionFn(finalCmd, finalArgs, toComplete) completions = append(completions, comps...) + } else { + // If there is no Go custom completion defined, check for legacy bash + // custom completion to preserve backwards-compatibility + completions, directive = checkLegacyCustomCompletion(finalCmd, finalArgs, flag, completions, directive) } return finalCmd, completions, directive, nil @@ -442,7 +453,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p if len(lastArg) > 0 && lastArg[0] == '-' { if index := strings.Index(lastArg, "="); index >= 0 { // Flag with an = - flagName = strings.TrimLeft(lastArg[:index], "-") + if strings.HasPrefix(lastArg[:index], "--") { + // Flag has full name + flagName = lastArg[2:index] + } else { + // Flag is shorthand + // We have to get the last shorthand flag name + // e.g. `-asd` => d to provide the correct completion + // https://github.com/spf13/cobra/issues/1257 + flagName = lastArg[index-1 : index] + } lastArg = lastArg[index+1:] flagWithEqual = true } else { @@ -459,8 +479,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p // If the flag contains an = it means it has already been fully processed, // so we don't need to deal with it here. if index := strings.Index(prevArg, "="); index < 0 { - flagName = strings.TrimLeft(prevArg, "-") - + if strings.HasPrefix(prevArg, "--") { + // Flag has full name + flagName = prevArg[2:] + } else { + // Flag is shorthand + // We have to get the last shorthand flag name + // e.g. `-asd` => d to provide the correct completion + // https://github.com/spf13/cobra/issues/1257 + flagName = prevArg[len(prevArg)-1:] + } // Remove the uncompleted flag or else there could be an error created // for an invalid value for that flag trimmedArgs = args[:len(args)-1] @@ -513,6 +541,65 @@ func findFlag(cmd *Command, name string) *pflag.Flag { return cmd.Flag(name) } +func nonCompletableFlag(flag *pflag.Flag) bool { + return flag.Hidden || len(flag.Deprecated) > 0 +} + +// This function checks if legacy bash custom completion should be performed and if so, +// it provides the shell script with the necessary information. +func checkLegacyCustomCompletion(cmd *Command, args []string, flag *pflag.Flag, completions []string, directive ShellCompDirective) ([]string, ShellCompDirective) { + // Check if any legacy custom completion is defined for the program + if len(cmd.Root().BashCompletionFunction) > 0 { + // Legacy custom completion is only triggered if no other completions were found. + if len(completions) == 0 { + if flag != nil { + // For legacy custom flag completion, we must let the script know the bash + // functions it should call based on the content of the annotation BashCompCustom. + if values, present := flag.Annotations[BashCompCustom]; present { + if len(values) > 0 { + handlers := strings.Join(values, "; ") + // We send the commands to set the shell variables that are needed + // for legacy custom completions followed by the functions to call + // to perform the actual flag completion + completions = append(prepareLegacyCustomCompletionVars(cmd, args), handlers) + directive = directive | shellCompDirectiveLegacyCustomComp + } + } + } else { + // Check if the legacy custom_func is defined. + // This check will work for both "__custom_func" and "__<program>_custom_func". + // This could happen if the program defined some functions for legacy flag completion + // but not the legacy custom_func. + if strings.Contains(cmd.Root().BashCompletionFunction, "_custom_func") { + // For legacy args completion, the script already knows what to call + // so we only need to tell it the commands to set the shell variables needed + completions = prepareLegacyCustomCompletionVars(cmd, args) + directive = directive | shellCompDirectiveLegacyCustomComp | shellCompDirectiveLegacyCustomArgsComp + } + } + } + } + return completions, directive +} + +// The original bash completion script had some shell variables that are used by legacy bash +// custom completions. Let's set those variables to allow those legacy custom completions +// to continue working. +func prepareLegacyCustomCompletionVars(cmd *Command, args []string) []string { + var compVarCmds []string + + // "last_command" variable + commandName := cmd.CommandPath() + commandName = strings.Replace(commandName, " ", "_", -1) + commandName = strings.Replace(commandName, ":", "__", -1) + compVarCmds = append(compVarCmds, fmt.Sprintf("last_command=%s", commandName)) + + // "nouns" array variable + compVarCmds = append(compVarCmds, fmt.Sprintf("nouns=(%s)", strings.Join(args, " "))) + + return compVarCmds +} + // CompDebug prints the specified string to the same file as where the // completion script prints its logs. // Note that completion printouts should never be on stdout as they would diff --git a/vendor/github.com/spf13/cobra/fish_completions.go b/vendor/github.com/spf13/cobra/fish_completions.go index eaae9bca8..aeaef0087 100644 --- a/vendor/github.com/spf13/cobra/fish_completions.go +++ b/vendor/github.com/spf13/cobra/fish_completions.go @@ -28,9 +28,9 @@ function __%[1]s_debug end function __%[1]s_perform_completion - __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv" + __%[1]s_debug "Starting __%[1]s_perform_completion" - set args (string split -- " " "$argv") + set args (string split -- " " (commandline -c)) set lastArg "$args[-1]" __%[1]s_debug "args: $args" @@ -71,31 +71,22 @@ function __%[1]s_perform_completion printf "%%s\n" "$directiveLine" end -# This function does three things: -# 1- Obtain the completions and store them in the global __%[1]s_comp_results -# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed -# and unset it otherwise -# 3- Return true if the completion results are not empty +# This function does two things: +# - Obtain the completions and store them in the global __%[1]s_comp_results +# - Return false if file completion should be performed function __%[1]s_prepare_completions + __%[1]s_debug "" + __%[1]s_debug "========= starting completion logic ==========" + # Start fresh - set --erase __%[1]s_comp_do_file_comp set --erase __%[1]s_comp_results - # Check if the command-line is already provided. This is useful for testing. - if not set --query __%[1]s_comp_commandLine - # Use the -c flag to allow for completion in the middle of the line - set __%[1]s_comp_commandLine (commandline -c) - end - __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine" - - set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine") - set --erase __%[1]s_comp_commandLine + set results (__%[1]s_perform_completion) __%[1]s_debug "Completion results: $results" if test -z "$results" __%[1]s_debug "No completion, probably due to a failure" # Might as well do file completion, in case it helps - set --global __%[1]s_comp_do_file_comp 1 return 1 end @@ -110,6 +101,8 @@ function __%[1]s_prepare_completions set shellCompDirectiveNoFileComp %[6]d set shellCompDirectiveFilterFileExt %[7]d set shellCompDirectiveFilterDirs %[8]d + set shellCompDirectiveLegacyCustomComp %[9]d + set shellCompDirectiveLegacyCustomArgsComp %[10]d if test -z "$directive" set directive 0 @@ -119,6 +112,14 @@ function __%[1]s_prepare_completions if test $compErr -eq 1 __%[1]s_debug "Received error directive: aborting." # Might as well do file completion, in case it helps + return 1 + end + + set legacyCustom (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomComp) %% 2) + set legacyCustomArgs (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomArgsComp) %% 2) + if test $legacyCustom -eq 1; or test $legacyCustomArgs -eq 1 + __%[1]s_debug "Legacy bash custom completion not applicable to fish" + # Do full file completion instead set --global __%[1]s_comp_do_file_comp 1 return 1 end @@ -128,7 +129,6 @@ function __%[1]s_prepare_completions if test $filefilter -eq 1; or test $dirfilter -eq 1 __%[1]s_debug "File extension filtering or directory filtering not supported" # Do full file completion instead - set --global __%[1]s_comp_do_file_comp 1 return 1 end @@ -137,27 +137,51 @@ function __%[1]s_prepare_completions __%[1]s_debug "nospace: $nospace, nofiles: $nofiles" - # Important not to quote the variable for count to work - set numComps (count $__%[1]s_comp_results) - __%[1]s_debug "numComps: $numComps" - - if test $numComps -eq 1; and test $nospace -ne 0 - # To support the "nospace" directive we trick the shell - # by outputting an extra, longer completion. - __%[1]s_debug "Adding second completion to perform nospace directive" - set --append __%[1]s_comp_results $__%[1]s_comp_results[1]. + # If we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set prefix (commandline -t) + __%[1]s_debug "prefix: $prefix" + + set completions + for comp in $__%[1]s_comp_results + if test (string match -e -r "^$prefix" "$comp") + set -a completions $comp + end + end + set --global __%[1]s_comp_results $completions + __%[1]s_debug "Filtered completions are: $__%[1]s_comp_results" + + # Important not to quote the variable for count to work + set numComps (count $__%[1]s_comp_results) + __%[1]s_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # To support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + # We must first split on \t to get rid of the descriptions because + # the extra character we add to the fake second completion must be + # before the description. We don't need descriptions anyway since + # there is only a single real completion which the shell will expand + # immediately. + __%[1]s_debug "Adding second completion to perform nospace directive" + set split (string split --max 1 \t $__%[1]s_comp_results[1]) + set --global __%[1]s_comp_results $split[1] $split[1]. + __%[1]s_debug "Completions are now: $__%[1]s_comp_results" + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __%[1]s_debug "Requesting file completion" + return 1 + end end - if test $numComps -eq 0; and test $nofiles -eq 0 - __%[1]s_debug "Requesting file completion" - set --global __%[1]s_comp_do_file_comp 1 - end - - # If we don't want file completion, we must return true even if there - # are no completions found. This is because fish will perform the last - # completion command, even if its condition is false, if no other - # completion command was triggered - return (not set --query __%[1]s_comp_do_file_comp) + return 0 end # Since Fish completions are only loaded once the user triggers them, we trigger them ourselves @@ -170,21 +194,14 @@ complete --do-complete "%[2]s " > /dev/null 2>&1 # Remove any pre-existing completions for the program since we will be handling all of them. complete -c %[2]s -e -# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions -# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable. -# -# This completion will be run second as complete commands are added FILO. -# It triggers file completion choices when __%[1]s_comp_do_file_comp is set. -complete -c %[2]s -n 'set --query __%[1]s_comp_do_file_comp' - -# This completion will be run first as complete commands are added FILO. -# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results and __%[1]s_comp_do_file_comp. -# It provides the program's completion choices. +# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results +# which provides the program's completion choices. complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' `, nameForVar, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) } // GenFishCompletion generates fish completion file and writes to the passed writer. diff --git a/vendor/github.com/spf13/cobra/zsh_completions.go b/vendor/github.com/spf13/cobra/zsh_completions.go index 92a70394a..ef290e2eb 100644 --- a/vendor/github.com/spf13/cobra/zsh_completions.go +++ b/vendor/github.com/spf13/cobra/zsh_completions.go @@ -94,8 +94,10 @@ _%[1]s() local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d + local shellCompDirectiveLegacyCustomComp=%[8]d + local shellCompDirectiveLegacyCustomArgsComp=%[9]d - local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace local -a completions __%[1]s_debug "\n========= starting completion logic ==========" @@ -163,7 +165,6 @@ _%[1]s() return fi - compCount=0 while IFS='\n' read -r comp; do if [ -n "$comp" ]; then # If requested, completions are returned with a description. @@ -175,13 +176,17 @@ _%[1]s() local tab=$(printf '\t') comp=${comp//$tab/:} - ((compCount++)) __%[1]s_debug "Adding completion: ${comp}" completions+=${comp} lastComp=$comp fi done < <(printf "%%s\n" "${out[@]}") + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __%[1]s_debug "Activating nospace." + noSpace="-S ''" + fi + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local filteringCmd @@ -208,25 +213,40 @@ _%[1]s() __%[1]s_debug "Listing directories in ." fi + local result _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? if [ -n "$subdir" ]; then popd >/dev/null 2>&1 fi - elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then - __%[1]s_debug "Activating nospace." - # We can use compadd here as there is no description when - # there is only one completion. - compadd -S '' "${lastComp}" - elif [ ${compCount} -eq 0 ]; then - if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then - __%[1]s_debug "deactivating file completion" + return $result + else + __%[1]s_debug "Calling _describe" + if eval _describe "completions" completions $flagPrefix $noSpace; then + __%[1]s_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 else - # Perform file completion - __%[1]s_debug "activating file completion" - _arguments '*:filename:_files'" ${flagPrefix}" + __%[1]s_debug "_describe did not find completions." + __%[1]s_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __%[1]s_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __%[1]s_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi fi - else - _describe "completions" completions $(echo $flagPrefix) fi } @@ -236,5 +256,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then fi `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) } diff --git a/vendor/go.uber.org/atomic/.codecov.yml b/vendor/go.uber.org/atomic/.codecov.yml index 6d4d1be7b..571116cc3 100644 --- a/vendor/go.uber.org/atomic/.codecov.yml +++ b/vendor/go.uber.org/atomic/.codecov.yml @@ -13,3 +13,7 @@ coverage: if_not_found: success # if parent is not found report status as success, error, or failure if_ci_failed: error # if ci fails report status as success, error, or failure +# Also update COVER_IGNORE_PKGS in the Makefile. +ignore: + - /internal/gen-atomicint/ + - /internal/gen-valuewrapper/ diff --git a/vendor/go.uber.org/atomic/.gitignore b/vendor/go.uber.org/atomic/.gitignore index 0a4504f11..c3fa25389 100644 --- a/vendor/go.uber.org/atomic/.gitignore +++ b/vendor/go.uber.org/atomic/.gitignore @@ -1,6 +1,7 @@ +/bin .DS_Store /vendor -/cover +cover.html cover.out lint.log diff --git a/vendor/go.uber.org/atomic/.travis.yml b/vendor/go.uber.org/atomic/.travis.yml index 0f3769e5f..13d0a4f25 100644 --- a/vendor/go.uber.org/atomic/.travis.yml +++ b/vendor/go.uber.org/atomic/.travis.yml @@ -2,26 +2,26 @@ sudo: false language: go go_import_path: go.uber.org/atomic -go: - - 1.11.x - - 1.12.x +env: + global: + - GO111MODULE=on matrix: include: - - go: 1.12.x - env: NO_TEST=yes LINT=yes + - go: oldstable + - go: stable + env: LINT=1 cache: directories: - vendor -install: - - make install_ci +before_install: + - go version script: - - test -n "$NO_TEST" || make test_ci - - test -n "$NO_TEST" || scripts/test-ubergo.sh - - test -z "$LINT" || make install_lint lint + - test -z "$LINT" || make lint + - make cover after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/go.uber.org/atomic/CHANGELOG.md b/vendor/go.uber.org/atomic/CHANGELOG.md new file mode 100644 index 000000000..24c0274dc --- /dev/null +++ b/vendor/go.uber.org/atomic/CHANGELOG.md @@ -0,0 +1,76 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.7.0] - 2020-09-14 +### Added +- Support JSON serialization and deserialization of primitive atomic types. +- Support Text marshalling and unmarshalling for string atomics. + +### Changed +- Disallow incorrect comparison of atomic values in a non-atomic way. + +### Removed +- Remove dependency on `golang.org/x/{lint, tools}`. + +## [1.6.0] - 2020-02-24 +### Changed +- Drop library dependency on `golang.org/x/{lint, tools}`. + +## [1.5.1] - 2019-11-19 +- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together + causing `CAS` to fail even though the old value matches. + +## [1.5.0] - 2019-10-29 +### Changed +- With Go modules, only the `go.uber.org/atomic` import path is supported now. + If you need to use the old import path, please add a `replace` directive to + your `go.mod`. + +## [1.4.0] - 2019-05-01 +### Added + - Add `atomic.Error` type for atomic operations on `error` values. + +## [1.3.2] - 2018-05-02 +### Added +- Add `atomic.Duration` type for atomic operations on `time.Duration` values. + +## [1.3.1] - 2017-11-14 +### Fixed +- Revert optimization for `atomic.String.Store("")` which caused data races. + +## [1.3.0] - 2017-11-13 +### Added +- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools. + +### Changed +- Optimize `atomic.String.Store("")` by avoiding an allocation. + +## [1.2.0] - 2017-04-12 +### Added +- Shadow `atomic.Value` from `sync/atomic`. + +## [1.1.0] - 2017-03-10 +### Added +- Add atomic `Float64` type. + +### Changed +- Support new `go.uber.org/atomic` import path. + +## [1.0.0] - 2016-07-18 + +- Initial release. + +[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0 +[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1 +[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0 +[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0 +[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2 +[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0 diff --git a/vendor/go.uber.org/atomic/Makefile b/vendor/go.uber.org/atomic/Makefile index 1ef263075..1b1376d42 100644 --- a/vendor/go.uber.org/atomic/Makefile +++ b/vendor/go.uber.org/atomic/Makefile @@ -1,51 +1,78 @@ -# Many Go tools take file globs or directories as arguments instead of packages. -PACKAGE_FILES ?= *.go +# Directory to place `go install`ed binaries into. +export GOBIN ?= $(shell pwd)/bin -# For pre go1.6 -export GO15VENDOREXPERIMENT=1 +GOLINT = $(GOBIN)/golint +GEN_ATOMICINT = $(GOBIN)/gen-atomicint +GEN_ATOMICWRAPPER = $(GOBIN)/gen-atomicwrapper +STATICCHECK = $(GOBIN)/staticcheck +GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print) + +# Also update ignore section in .codecov.yml. +COVER_IGNORE_PKGS = \ + go.uber.org/atomic/internal/gen-atomicint \ + go.uber.org/atomic/internal/gen-atomicwrapper .PHONY: build build: - go build -i ./... + go build ./... +.PHONY: test +test: + go test -race ./... -.PHONY: install -install: - glide --version || go get github.com/Masterminds/glide - glide install +.PHONY: gofmt +gofmt: + $(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX)) + gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true + @[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false) +$(GOLINT): + cd tools && go install golang.org/x/lint/golint -.PHONY: test -test: - go test -cover -race ./... +$(STATICCHECK): + cd tools && go install honnef.co/go/tools/cmd/staticcheck +$(GEN_ATOMICWRAPPER): $(wildcard ./internal/gen-atomicwrapper/*) + go build -o $@ ./internal/gen-atomicwrapper -.PHONY: install_ci -install_ci: install - go get github.com/wadey/gocovmerge - go get github.com/mattn/goveralls - go get golang.org/x/tools/cmd/cover +$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*) + go build -o $@ ./internal/gen-atomicint -.PHONY: install_lint -install_lint: - go get golang.org/x/lint/golint +.PHONY: golint +golint: $(GOLINT) + $(GOLINT) ./... +.PHONY: staticcheck +staticcheck: $(STATICCHECK) + $(STATICCHECK) ./... .PHONY: lint -lint: - @rm -rf lint.log - @echo "Checking formatting..." - @gofmt -d -s $(PACKAGE_FILES) 2>&1 | tee lint.log - @echo "Checking vet..." - @go vet ./... 2>&1 | tee -a lint.log;) - @echo "Checking lint..." - @golint $$(go list ./...) 2>&1 | tee -a lint.log - @echo "Checking for unresolved FIXMEs..." - @git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log - @[ ! -s lint.log ] - - -.PHONY: test_ci -test_ci: install_ci build - ./scripts/cover.sh $(shell go list $(PACKAGES)) +lint: gofmt golint staticcheck generatenodirty + +# comma separated list of packages to consider for code coverage. +COVER_PKG = $(shell \ + go list -find ./... | \ + grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \ + paste -sd, -) + +.PHONY: cover +cover: + go test -coverprofile=cover.out -coverpkg $(COVER_PKG) -v ./... + go tool cover -html=cover.out -o cover.html + +.PHONY: generate +generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER) + go generate ./... + +.PHONY: generatenodirty +generatenodirty: + @[ -z "$$(git status --porcelain)" ] || ( \ + echo "Working tree is dirty. Commit your changes first."; \ + exit 1 ) + @make generate + @status=$$(git status --porcelain); \ + [ -z "$$status" ] || ( \ + echo "Working tree is dirty after `make generate`:"; \ + echo "$$status"; \ + echo "Please ensure that the generated code is up-to-date." ) diff --git a/vendor/go.uber.org/atomic/README.md b/vendor/go.uber.org/atomic/README.md index 62eb8e576..ade0c20f1 100644 --- a/vendor/go.uber.org/atomic/README.md +++ b/vendor/go.uber.org/atomic/README.md @@ -3,9 +3,34 @@ Simple wrappers for primitive types to enforce atomic access. ## Installation -`go get -u go.uber.org/atomic` + +```shell +$ go get -u go.uber.org/atomic@v1 +``` + +### Legacy Import Path + +As of v1.5.0, the import path `go.uber.org/atomic` is the only supported way +of using this package. If you are using Go modules, this package will fail to +compile with the legacy import path path `github.com/uber-go/atomic`. + +We recommend migrating your code to the new import path but if you're unable +to do so, or if your dependencies are still using the old import path, you +will have to add a `replace` directive to your `go.mod` file downgrading the +legacy import path to an older version. + +``` +replace github.com/uber-go/atomic => github.com/uber-go/atomic v1.4.0 +``` + +You can do so automatically by running the following command. + +```shell +$ go mod edit -replace github.com/uber-go/atomic=github.com/uber-go/atomic@v1.4.0 +``` ## Usage + The standard library's `sync/atomic` is powerful, but it's easy to forget which variables must be accessed atomically. `go.uber.org/atomic` preserves all the functionality of the standard library, but wraps the primitive types to @@ -21,9 +46,11 @@ atom.CAS(40, 11) See the [documentation][doc] for a complete API specification. ## Development Status + Stable. -___ +--- + Released under the [MIT License](LICENSE.txt). [doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg diff --git a/vendor/go.uber.org/atomic/atomic.go b/vendor/go.uber.org/atomic/atomic.go deleted file mode 100644 index 1db6849fc..000000000 --- a/vendor/go.uber.org/atomic/atomic.go +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) 2016 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Package atomic provides simple wrappers around numerics to enforce atomic -// access. -package atomic - -import ( - "math" - "sync/atomic" - "time" -) - -// Int32 is an atomic wrapper around an int32. -type Int32 struct{ v int32 } - -// NewInt32 creates an Int32. -func NewInt32(i int32) *Int32 { - return &Int32{i} -} - -// Load atomically loads the wrapped value. -func (i *Int32) Load() int32 { - return atomic.LoadInt32(&i.v) -} - -// Add atomically adds to the wrapped int32 and returns the new value. -func (i *Int32) Add(n int32) int32 { - return atomic.AddInt32(&i.v, n) -} - -// Sub atomically subtracts from the wrapped int32 and returns the new value. -func (i *Int32) Sub(n int32) int32 { - return atomic.AddInt32(&i.v, -n) -} - -// Inc atomically increments the wrapped int32 and returns the new value. -func (i *Int32) Inc() int32 { - return i.Add(1) -} - -// Dec atomically decrements the wrapped int32 and returns the new value. -func (i *Int32) Dec() int32 { - return i.Sub(1) -} - -// CAS is an atomic compare-and-swap. -func (i *Int32) CAS(old, new int32) bool { - return atomic.CompareAndSwapInt32(&i.v, old, new) -} - -// Store atomically stores the passed value. -func (i *Int32) Store(n int32) { - atomic.StoreInt32(&i.v, n) -} - -// Swap atomically swaps the wrapped int32 and returns the old value. -func (i *Int32) Swap(n int32) int32 { - return atomic.SwapInt32(&i.v, n) -} - -// Int64 is an atomic wrapper around an int64. -type Int64 struct{ v int64 } - -// NewInt64 creates an Int64. -func NewInt64(i int64) *Int64 { - return &Int64{i} -} - -// Load atomically loads the wrapped value. -func (i *Int64) Load() int64 { - return atomic.LoadInt64(&i.v) -} - -// Add atomically adds to the wrapped int64 and returns the new value. -func (i *Int64) Add(n int64) int64 { - return atomic.AddInt64(&i.v, n) -} - -// Sub atomically subtracts from the wrapped int64 and returns the new value. -func (i *Int64) Sub(n int64) int64 { - return atomic.AddInt64(&i.v, -n) -} - -// Inc atomically increments the wrapped int64 and returns the new value. -func (i *Int64) Inc() int64 { - return i.Add(1) -} - -// Dec atomically decrements the wrapped int64 and returns the new value. -func (i *Int64) Dec() int64 { - return i.Sub(1) -} - -// CAS is an atomic compare-and-swap. -func (i *Int64) CAS(old, new int64) bool { - return atomic.CompareAndSwapInt64(&i.v, old, new) -} - -// Store atomically stores the passed value. -func (i *Int64) Store(n int64) { - atomic.StoreInt64(&i.v, n) -} - -// Swap atomically swaps the wrapped int64 and returns the old value. -func (i *Int64) Swap(n int64) int64 { - return atomic.SwapInt64(&i.v, n) -} - -// Uint32 is an atomic wrapper around an uint32. -type Uint32 struct{ v uint32 } - -// NewUint32 creates a Uint32. -func NewUint32(i uint32) *Uint32 { - return &Uint32{i} -} - -// Load atomically loads the wrapped value. -func (i *Uint32) Load() uint32 { - return atomic.LoadUint32(&i.v) -} - -// Add atomically adds to the wrapped uint32 and returns the new value. -func (i *Uint32) Add(n uint32) uint32 { - return atomic.AddUint32(&i.v, n) -} - -// Sub atomically subtracts from the wrapped uint32 and returns the new value. -func (i *Uint32) Sub(n uint32) uint32 { - return atomic.AddUint32(&i.v, ^(n - 1)) -} - -// Inc atomically increments the wrapped uint32 and returns the new value. -func (i *Uint32) Inc() uint32 { - return i.Add(1) -} - -// Dec atomically decrements the wrapped int32 and returns the new value. -func (i *Uint32) Dec() uint32 { - return i.Sub(1) -} - -// CAS is an atomic compare-and-swap. -func (i *Uint32) CAS(old, new uint32) bool { - return atomic.CompareAndSwapUint32(&i.v, old, new) -} - -// Store atomically stores the passed value. -func (i *Uint32) Store(n uint32) { - atomic.StoreUint32(&i.v, n) -} - -// Swap atomically swaps the wrapped uint32 and returns the old value. -func (i *Uint32) Swap(n uint32) uint32 { - return atomic.SwapUint32(&i.v, n) -} - -// Uint64 is an atomic wrapper around a uint64. -type Uint64 struct{ v uint64 } - -// NewUint64 creates a Uint64. -func NewUint64(i uint64) *Uint64 { - return &Uint64{i} -} - -// Load atomically loads the wrapped value. -func (i *Uint64) Load() uint64 { - return atomic.LoadUint64(&i.v) -} - -// Add atomically adds to the wrapped uint64 and returns the new value. -func (i *Uint64) Add(n uint64) uint64 { - return atomic.AddUint64(&i.v, n) -} - -// Sub atomically subtracts from the wrapped uint64 and returns the new value. -func (i *Uint64) Sub(n uint64) uint64 { - return atomic.AddUint64(&i.v, ^(n - 1)) -} - -// Inc atomically increments the wrapped uint64 and returns the new value. -func (i *Uint64) Inc() uint64 { - return i.Add(1) -} - -// Dec atomically decrements the wrapped uint64 and returns the new value. -func (i *Uint64) Dec() uint64 { - return i.Sub(1) -} - -// CAS is an atomic compare-and-swap. -func (i *Uint64) CAS(old, new uint64) bool { - return atomic.CompareAndSwapUint64(&i.v, old, new) -} - -// Store atomically stores the passed value. -func (i *Uint64) Store(n uint64) { - atomic.StoreUint64(&i.v, n) -} - -// Swap atomically swaps the wrapped uint64 and returns the old value. -func (i *Uint64) Swap(n uint64) uint64 { - return atomic.SwapUint64(&i.v, n) -} - -// Bool is an atomic Boolean. -type Bool struct{ v uint32 } - -// NewBool creates a Bool. -func NewBool(initial bool) *Bool { - return &Bool{boolToInt(initial)} -} - -// Load atomically loads the Boolean. -func (b *Bool) Load() bool { - return truthy(atomic.LoadUint32(&b.v)) -} - -// CAS is an atomic compare-and-swap. -func (b *Bool) CAS(old, new bool) bool { - return atomic.CompareAndSwapUint32(&b.v, boolToInt(old), boolToInt(new)) -} - -// Store atomically stores the passed value. -func (b *Bool) Store(new bool) { - atomic.StoreUint32(&b.v, boolToInt(new)) -} - -// Swap sets the given value and returns the previous value. -func (b *Bool) Swap(new bool) bool { - return truthy(atomic.SwapUint32(&b.v, boolToInt(new))) -} - -// Toggle atomically negates the Boolean and returns the previous value. -func (b *Bool) Toggle() bool { - return truthy(atomic.AddUint32(&b.v, 1) - 1) -} - -func truthy(n uint32) bool { - return n&1 == 1 -} - -func boolToInt(b bool) uint32 { - if b { - return 1 - } - return 0 -} - -// Float64 is an atomic wrapper around float64. -type Float64 struct { - v uint64 -} - -// NewFloat64 creates a Float64. -func NewFloat64(f float64) *Float64 { - return &Float64{math.Float64bits(f)} -} - -// Load atomically loads the wrapped value. -func (f *Float64) Load() float64 { - return math.Float64frombits(atomic.LoadUint64(&f.v)) -} - -// Store atomically stores the passed value. -func (f *Float64) Store(s float64) { - atomic.StoreUint64(&f.v, math.Float64bits(s)) -} - -// Add atomically adds to the wrapped float64 and returns the new value. -func (f *Float64) Add(s float64) float64 { - for { - old := f.Load() - new := old + s - if f.CAS(old, new) { - return new - } - } -} - -// Sub atomically subtracts from the wrapped float64 and returns the new value. -func (f *Float64) Sub(s float64) float64 { - return f.Add(-s) -} - -// CAS is an atomic compare-and-swap. -func (f *Float64) CAS(old, new float64) bool { - return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new)) -} - -// Duration is an atomic wrapper around time.Duration -// https://godoc.org/time#Duration -type Duration struct { - v Int64 -} - -// NewDuration creates a Duration. -func NewDuration(d time.Duration) *Duration { - return &Duration{v: *NewInt64(int64(d))} -} - -// Load atomically loads the wrapped value. -func (d *Duration) Load() time.Duration { - return time.Duration(d.v.Load()) -} - -// Store atomically stores the passed value. -func (d *Duration) Store(n time.Duration) { - d.v.Store(int64(n)) -} - -// Add atomically adds to the wrapped time.Duration and returns the new value. -func (d *Duration) Add(n time.Duration) time.Duration { - return time.Duration(d.v.Add(int64(n))) -} - -// Sub atomically subtracts from the wrapped time.Duration and returns the new value. -func (d *Duration) Sub(n time.Duration) time.Duration { - return time.Duration(d.v.Sub(int64(n))) -} - -// Swap atomically swaps the wrapped time.Duration and returns the old value. -func (d *Duration) Swap(n time.Duration) time.Duration { - return time.Duration(d.v.Swap(int64(n))) -} - -// CAS is an atomic compare-and-swap. -func (d *Duration) CAS(old, new time.Duration) bool { - return d.v.CAS(int64(old), int64(new)) -} - -// Value shadows the type of the same name from sync/atomic -// https://godoc.org/sync/atomic#Value -type Value struct{ atomic.Value } diff --git a/vendor/go.uber.org/atomic/bool.go b/vendor/go.uber.org/atomic/bool.go new file mode 100644 index 000000000..9cf1914b1 --- /dev/null +++ b/vendor/go.uber.org/atomic/bool.go @@ -0,0 +1,81 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" +) + +// Bool is an atomic type-safe wrapper for bool values. +type Bool struct { + _ nocmp // disallow non-atomic comparison + + v Uint32 +} + +var _zeroBool bool + +// NewBool creates a new Bool. +func NewBool(v bool) *Bool { + x := &Bool{} + if v != _zeroBool { + x.Store(v) + } + return x +} + +// Load atomically loads the wrapped bool. +func (x *Bool) Load() bool { + return truthy(x.v.Load()) +} + +// Store atomically stores the passed bool. +func (x *Bool) Store(v bool) { + x.v.Store(boolToInt(v)) +} + +// CAS is an atomic compare-and-swap for bool values. +func (x *Bool) CAS(o, n bool) bool { + return x.v.CAS(boolToInt(o), boolToInt(n)) +} + +// Swap atomically stores the given bool and returns the old +// value. +func (x *Bool) Swap(o bool) bool { + return truthy(x.v.Swap(boolToInt(o))) +} + +// MarshalJSON encodes the wrapped bool into JSON. +func (x *Bool) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a bool from JSON. +func (x *Bool) UnmarshalJSON(b []byte) error { + var v bool + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/bool_ext.go b/vendor/go.uber.org/atomic/bool_ext.go new file mode 100644 index 000000000..c7bf7a827 --- /dev/null +++ b/vendor/go.uber.org/atomic/bool_ext.go @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go + +func truthy(n uint32) bool { + return n == 1 +} + +func boolToInt(b bool) uint32 { + if b { + return 1 + } + return 0 +} + +// Toggle atomically negates the Boolean and returns the previous value. +func (b *Bool) Toggle() bool { + for { + old := b.Load() + if b.CAS(old, !old) { + return old + } + } +} + +// String encodes the wrapped value as a string. +func (b *Bool) String() string { + return strconv.FormatBool(b.Load()) +} diff --git a/vendor/go.uber.org/atomic/doc.go b/vendor/go.uber.org/atomic/doc.go new file mode 100644 index 000000000..ae7390ee6 --- /dev/null +++ b/vendor/go.uber.org/atomic/doc.go @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package atomic provides simple wrappers around numerics to enforce atomic +// access. +package atomic diff --git a/vendor/go.uber.org/atomic/duration.go b/vendor/go.uber.org/atomic/duration.go new file mode 100644 index 000000000..027cfcb20 --- /dev/null +++ b/vendor/go.uber.org/atomic/duration.go @@ -0,0 +1,82 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "time" +) + +// Duration is an atomic type-safe wrapper for time.Duration values. +type Duration struct { + _ nocmp // disallow non-atomic comparison + + v Int64 +} + +var _zeroDuration time.Duration + +// NewDuration creates a new Duration. +func NewDuration(v time.Duration) *Duration { + x := &Duration{} + if v != _zeroDuration { + x.Store(v) + } + return x +} + +// Load atomically loads the wrapped time.Duration. +func (x *Duration) Load() time.Duration { + return time.Duration(x.v.Load()) +} + +// Store atomically stores the passed time.Duration. +func (x *Duration) Store(v time.Duration) { + x.v.Store(int64(v)) +} + +// CAS is an atomic compare-and-swap for time.Duration values. +func (x *Duration) CAS(o, n time.Duration) bool { + return x.v.CAS(int64(o), int64(n)) +} + +// Swap atomically stores the given time.Duration and returns the old +// value. +func (x *Duration) Swap(o time.Duration) time.Duration { + return time.Duration(x.v.Swap(int64(o))) +} + +// MarshalJSON encodes the wrapped time.Duration into JSON. +func (x *Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a time.Duration from JSON. +func (x *Duration) UnmarshalJSON(b []byte) error { + var v time.Duration + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/duration_ext.go b/vendor/go.uber.org/atomic/duration_ext.go new file mode 100644 index 000000000..6273b66bd --- /dev/null +++ b/vendor/go.uber.org/atomic/duration_ext.go @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import "time" + +//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go + +// Add atomically adds to the wrapped time.Duration and returns the new value. +func (d *Duration) Add(n time.Duration) time.Duration { + return time.Duration(d.v.Add(int64(n))) +} + +// Sub atomically subtracts from the wrapped time.Duration and returns the new value. +func (d *Duration) Sub(n time.Duration) time.Duration { + return time.Duration(d.v.Sub(int64(n))) +} + +// String encodes the wrapped value as a string. +func (d *Duration) String() string { + return d.Load().String() +} diff --git a/vendor/go.uber.org/atomic/error.go b/vendor/go.uber.org/atomic/error.go index 0489d19ba..a6166fbea 100644 --- a/vendor/go.uber.org/atomic/error.go +++ b/vendor/go.uber.org/atomic/error.go @@ -1,4 +1,6 @@ -// Copyright (c) 2016 Uber Technologies, Inc. +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,36 +22,30 @@ package atomic -// Error is an atomic type-safe wrapper around Value for errors -type Error struct{ v Value } - -// errorHolder is non-nil holder for error object. -// atomic.Value panics on saving nil object, so err object needs to be -// wrapped with valid object first. -type errorHolder struct{ err error } +// Error is an atomic type-safe wrapper for error values. +type Error struct { + _ nocmp // disallow non-atomic comparison -// NewError creates new atomic error object -func NewError(err error) *Error { - e := &Error{} - if err != nil { - e.Store(err) - } - return e + v Value } -// Load atomically loads the wrapped error -func (e *Error) Load() error { - v := e.v.Load() - if v == nil { - return nil +var _zeroError error + +// NewError creates a new Error. +func NewError(v error) *Error { + x := &Error{} + if v != _zeroError { + x.Store(v) } + return x +} - eh := v.(errorHolder) - return eh.err +// Load atomically loads the wrapped error. +func (x *Error) Load() error { + return unpackError(x.v.Load()) } -// Store atomically stores error. -// NOTE: a holder object is allocated on each Store call. -func (e *Error) Store(err error) { - e.v.Store(errorHolder{err: err}) +// Store atomically stores the passed error. +func (x *Error) Store(v error) { + x.v.Store(packError(v)) } diff --git a/vendor/go.uber.org/atomic/error_ext.go b/vendor/go.uber.org/atomic/error_ext.go new file mode 100644 index 000000000..ffe0be21c --- /dev/null +++ b/vendor/go.uber.org/atomic/error_ext.go @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// atomic.Value panics on nil inputs, or if the underlying type changes. +// Stabilize by always storing a custom struct that we control. + +//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go + +type packedError struct{ Value error } + +func packError(v error) interface{} { + return packedError{v} +} + +func unpackError(v interface{}) error { + if err, ok := v.(packedError); ok { + return err.Value + } + return nil +} diff --git a/vendor/go.uber.org/atomic/float64.go b/vendor/go.uber.org/atomic/float64.go new file mode 100644 index 000000000..071906020 --- /dev/null +++ b/vendor/go.uber.org/atomic/float64.go @@ -0,0 +1,76 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "math" +) + +// Float64 is an atomic type-safe wrapper for float64 values. +type Float64 struct { + _ nocmp // disallow non-atomic comparison + + v Uint64 +} + +var _zeroFloat64 float64 + +// NewFloat64 creates a new Float64. +func NewFloat64(v float64) *Float64 { + x := &Float64{} + if v != _zeroFloat64 { + x.Store(v) + } + return x +} + +// Load atomically loads the wrapped float64. +func (x *Float64) Load() float64 { + return math.Float64frombits(x.v.Load()) +} + +// Store atomically stores the passed float64. +func (x *Float64) Store(v float64) { + x.v.Store(math.Float64bits(v)) +} + +// CAS is an atomic compare-and-swap for float64 values. +func (x *Float64) CAS(o, n float64) bool { + return x.v.CAS(math.Float64bits(o), math.Float64bits(n)) +} + +// MarshalJSON encodes the wrapped float64 into JSON. +func (x *Float64) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a float64 from JSON. +func (x *Float64) UnmarshalJSON(b []byte) error { + var v float64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/float64_ext.go b/vendor/go.uber.org/atomic/float64_ext.go new file mode 100644 index 000000000..927b1add7 --- /dev/null +++ b/vendor/go.uber.org/atomic/float64_ext.go @@ -0,0 +1,47 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import "strconv" + +//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -cas -json -imports math -file=float64.go + +// Add atomically adds to the wrapped float64 and returns the new value. +func (f *Float64) Add(s float64) float64 { + for { + old := f.Load() + new := old + s + if f.CAS(old, new) { + return new + } + } +} + +// Sub atomically subtracts from the wrapped float64 and returns the new value. +func (f *Float64) Sub(s float64) float64 { + return f.Add(-s) +} + +// String encodes the wrapped value as a string. +func (f *Float64) String() string { + // 'g' is the behavior for floats with %v. + return strconv.FormatFloat(f.Load(), 'g', -1, 64) +} diff --git a/vendor/go.uber.org/atomic/gen.go b/vendor/go.uber.org/atomic/gen.go new file mode 100644 index 000000000..50d6b2485 --- /dev/null +++ b/vendor/go.uber.org/atomic/gen.go @@ -0,0 +1,26 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go +//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go +//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go +//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go diff --git a/vendor/go.uber.org/atomic/glide.lock b/vendor/go.uber.org/atomic/glide.lock deleted file mode 100644 index 3c72c5997..000000000 --- a/vendor/go.uber.org/atomic/glide.lock +++ /dev/null @@ -1,17 +0,0 @@ -hash: f14d51408e3e0e4f73b34e4039484c78059cd7fc5f4996fdd73db20dc8d24f53 -updated: 2016-10-27T00:10:51.16960137-07:00 -imports: [] -testImports: -- name: github.com/davecgh/go-spew - version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d - subpackages: - - spew -- name: github.com/pmezard/go-difflib - version: d8ed2627bdf02c080bf22230dbb337003b7aba2d - subpackages: - - difflib -- name: github.com/stretchr/testify - version: d77da356e56a7428ad25149ca77381849a6a5232 - subpackages: - - assert - - require diff --git a/vendor/go.uber.org/atomic/glide.yaml b/vendor/go.uber.org/atomic/glide.yaml deleted file mode 100644 index 4cf608ec0..000000000 --- a/vendor/go.uber.org/atomic/glide.yaml +++ /dev/null @@ -1,6 +0,0 @@ -package: go.uber.org/atomic -testImport: -- package: github.com/stretchr/testify - subpackages: - - assert - - require diff --git a/vendor/go.uber.org/atomic/go.mod b/vendor/go.uber.org/atomic/go.mod new file mode 100644 index 000000000..daa7599fe --- /dev/null +++ b/vendor/go.uber.org/atomic/go.mod @@ -0,0 +1,8 @@ +module go.uber.org/atomic + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/stretchr/testify v1.3.0 +) + +go 1.13 diff --git a/vendor/go.uber.org/atomic/go.sum b/vendor/go.uber.org/atomic/go.sum new file mode 100644 index 000000000..4f76e62c1 --- /dev/null +++ b/vendor/go.uber.org/atomic/go.sum @@ -0,0 +1,9 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/vendor/go.uber.org/atomic/int32.go b/vendor/go.uber.org/atomic/int32.go new file mode 100644 index 000000000..18ae56493 --- /dev/null +++ b/vendor/go.uber.org/atomic/int32.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Int32 is an atomic wrapper around int32. +type Int32 struct { + _ nocmp // disallow non-atomic comparison + + v int32 +} + +// NewInt32 creates a new Int32. +func NewInt32(i int32) *Int32 { + return &Int32{v: i} +} + +// Load atomically loads the wrapped value. +func (i *Int32) Load() int32 { + return atomic.LoadInt32(&i.v) +} + +// Add atomically adds to the wrapped int32 and returns the new value. +func (i *Int32) Add(n int32) int32 { + return atomic.AddInt32(&i.v, n) +} + +// Sub atomically subtracts from the wrapped int32 and returns the new value. +func (i *Int32) Sub(n int32) int32 { + return atomic.AddInt32(&i.v, -n) +} + +// Inc atomically increments the wrapped int32 and returns the new value. +func (i *Int32) Inc() int32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int32 and returns the new value. +func (i *Int32) Dec() int32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Int32) CAS(old, new int32) bool { + return atomic.CompareAndSwapInt32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int32) Store(n int32) { + atomic.StoreInt32(&i.v, n) +} + +// Swap atomically swaps the wrapped int32 and returns the old value. +func (i *Int32) Swap(n int32) int32 { + return atomic.SwapInt32(&i.v, n) +} + +// MarshalJSON encodes the wrapped int32 into JSON. +func (i *Int32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped int32. +func (i *Int32) UnmarshalJSON(b []byte) error { + var v int32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Int32) String() string { + v := i.Load() + return strconv.FormatInt(int64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/int64.go b/vendor/go.uber.org/atomic/int64.go new file mode 100644 index 000000000..2bcbbfaa9 --- /dev/null +++ b/vendor/go.uber.org/atomic/int64.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Int64 is an atomic wrapper around int64. +type Int64 struct { + _ nocmp // disallow non-atomic comparison + + v int64 +} + +// NewInt64 creates a new Int64. +func NewInt64(i int64) *Int64 { + return &Int64{v: i} +} + +// Load atomically loads the wrapped value. +func (i *Int64) Load() int64 { + return atomic.LoadInt64(&i.v) +} + +// Add atomically adds to the wrapped int64 and returns the new value. +func (i *Int64) Add(n int64) int64 { + return atomic.AddInt64(&i.v, n) +} + +// Sub atomically subtracts from the wrapped int64 and returns the new value. +func (i *Int64) Sub(n int64) int64 { + return atomic.AddInt64(&i.v, -n) +} + +// Inc atomically increments the wrapped int64 and returns the new value. +func (i *Int64) Inc() int64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int64 and returns the new value. +func (i *Int64) Dec() int64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Int64) CAS(old, new int64) bool { + return atomic.CompareAndSwapInt64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int64) Store(n int64) { + atomic.StoreInt64(&i.v, n) +} + +// Swap atomically swaps the wrapped int64 and returns the old value. +func (i *Int64) Swap(n int64) int64 { + return atomic.SwapInt64(&i.v, n) +} + +// MarshalJSON encodes the wrapped int64 into JSON. +func (i *Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped int64. +func (i *Int64) UnmarshalJSON(b []byte) error { + var v int64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Int64) String() string { + v := i.Load() + return strconv.FormatInt(int64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/nocmp.go b/vendor/go.uber.org/atomic/nocmp.go new file mode 100644 index 000000000..a8201cb4a --- /dev/null +++ b/vendor/go.uber.org/atomic/nocmp.go @@ -0,0 +1,35 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// nocmp is an uncomparable struct. Embed this inside another struct to make +// it uncomparable. +// +// type Foo struct { +// nocmp +// // ... +// } +// +// This DOES NOT: +// +// - Disallow shallow copies of structs +// - Disallow comparison of pointers to uncomparable structs +type nocmp [0]func() diff --git a/vendor/go.uber.org/atomic/string.go b/vendor/go.uber.org/atomic/string.go index ede8136fa..225b7a2be 100644 --- a/vendor/go.uber.org/atomic/string.go +++ b/vendor/go.uber.org/atomic/string.go @@ -1,4 +1,6 @@ -// Copyright (c) 2016 Uber Technologies, Inc. +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,30 +22,33 @@ package atomic -// String is an atomic type-safe wrapper around Value for strings. -type String struct{ v Value } +// String is an atomic type-safe wrapper for string values. +type String struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroString string -// NewString creates a String. -func NewString(str string) *String { - s := &String{} - if str != "" { - s.Store(str) +// NewString creates a new String. +func NewString(v string) *String { + x := &String{} + if v != _zeroString { + x.Store(v) } - return s + return x } // Load atomically loads the wrapped string. -func (s *String) Load() string { - v := s.v.Load() - if v == nil { - return "" +func (x *String) Load() string { + if v := x.v.Load(); v != nil { + return v.(string) } - return v.(string) + return _zeroString } // Store atomically stores the passed string. -// Note: Converting the string to an interface{} to store in the Value -// requires an allocation. -func (s *String) Store(str string) { - s.v.Store(str) +func (x *String) Store(v string) { + x.v.Store(v) } diff --git a/vendor/go.uber.org/atomic/string_ext.go b/vendor/go.uber.org/atomic/string_ext.go new file mode 100644 index 000000000..3a9558213 --- /dev/null +++ b/vendor/go.uber.org/atomic/string_ext.go @@ -0,0 +1,43 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go + +// String returns the wrapped value. +func (s *String) String() string { + return s.Load() +} + +// MarshalText encodes the wrapped string into a textual form. +// +// This makes it encodable as JSON, YAML, XML, and more. +func (s *String) MarshalText() ([]byte, error) { + return []byte(s.Load()), nil +} + +// UnmarshalText decodes text and replaces the wrapped string with it. +// +// This makes it decodable from JSON, YAML, XML, and more. +func (s *String) UnmarshalText(b []byte) error { + s.Store(string(b)) + return nil +} diff --git a/vendor/go.uber.org/atomic/uint32.go b/vendor/go.uber.org/atomic/uint32.go new file mode 100644 index 000000000..a973aba1a --- /dev/null +++ b/vendor/go.uber.org/atomic/uint32.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uint32 is an atomic wrapper around uint32. +type Uint32 struct { + _ nocmp // disallow non-atomic comparison + + v uint32 +} + +// NewUint32 creates a new Uint32. +func NewUint32(i uint32) *Uint32 { + return &Uint32{v: i} +} + +// Load atomically loads the wrapped value. +func (i *Uint32) Load() uint32 { + return atomic.LoadUint32(&i.v) +} + +// Add atomically adds to the wrapped uint32 and returns the new value. +func (i *Uint32) Add(n uint32) uint32 { + return atomic.AddUint32(&i.v, n) +} + +// Sub atomically subtracts from the wrapped uint32 and returns the new value. +func (i *Uint32) Sub(n uint32) uint32 { + return atomic.AddUint32(&i.v, ^(n - 1)) +} + +// Inc atomically increments the wrapped uint32 and returns the new value. +func (i *Uint32) Inc() uint32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint32 and returns the new value. +func (i *Uint32) Dec() uint32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uint32) CAS(old, new uint32) bool { + return atomic.CompareAndSwapUint32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint32) Store(n uint32) { + atomic.StoreUint32(&i.v, n) +} + +// Swap atomically swaps the wrapped uint32 and returns the old value. +func (i *Uint32) Swap(n uint32) uint32 { + return atomic.SwapUint32(&i.v, n) +} + +// MarshalJSON encodes the wrapped uint32 into JSON. +func (i *Uint32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uint32. +func (i *Uint32) UnmarshalJSON(b []byte) error { + var v uint32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uint32) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/uint64.go b/vendor/go.uber.org/atomic/uint64.go new file mode 100644 index 000000000..3b6c71fd5 --- /dev/null +++ b/vendor/go.uber.org/atomic/uint64.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uint64 is an atomic wrapper around uint64. +type Uint64 struct { + _ nocmp // disallow non-atomic comparison + + v uint64 +} + +// NewUint64 creates a new Uint64. +func NewUint64(i uint64) *Uint64 { + return &Uint64{v: i} +} + +// Load atomically loads the wrapped value. +func (i *Uint64) Load() uint64 { + return atomic.LoadUint64(&i.v) +} + +// Add atomically adds to the wrapped uint64 and returns the new value. +func (i *Uint64) Add(n uint64) uint64 { + return atomic.AddUint64(&i.v, n) +} + +// Sub atomically subtracts from the wrapped uint64 and returns the new value. +func (i *Uint64) Sub(n uint64) uint64 { + return atomic.AddUint64(&i.v, ^(n - 1)) +} + +// Inc atomically increments the wrapped uint64 and returns the new value. +func (i *Uint64) Inc() uint64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint64 and returns the new value. +func (i *Uint64) Dec() uint64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uint64) CAS(old, new uint64) bool { + return atomic.CompareAndSwapUint64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint64) Store(n uint64) { + atomic.StoreUint64(&i.v, n) +} + +// Swap atomically swaps the wrapped uint64 and returns the old value. +func (i *Uint64) Swap(n uint64) uint64 { + return atomic.SwapUint64(&i.v, n) +} + +// MarshalJSON encodes the wrapped uint64 into JSON. +func (i *Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uint64. +func (i *Uint64) UnmarshalJSON(b []byte) error { + var v uint64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uint64) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/value.go b/vendor/go.uber.org/atomic/value.go new file mode 100644 index 000000000..671f3a382 --- /dev/null +++ b/vendor/go.uber.org/atomic/value.go @@ -0,0 +1,31 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import "sync/atomic" + +// Value shadows the type of the same name from sync/atomic +// https://godoc.org/sync/atomic#Value +type Value struct { + atomic.Value + + _ nocmp // disallow non-atomic comparison +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6264a8d51..ff7f040e4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -41,6 +41,8 @@ github.com/buger/goterm # github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b github.com/checkpoint-restore/go-criu github.com/checkpoint-restore/go-criu/rpc +# github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e +github.com/chzyer/readline # github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f github.com/containerd/cgroups/stats/v1 # github.com/containerd/containerd v1.4.1 @@ -66,7 +68,7 @@ github.com/containernetworking/plugins/pkg/utils/hwaddr github.com/containernetworking/plugins/pkg/utils/sysctl github.com/containernetworking/plugins/plugins/ipam/host-local/backend github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator -# github.com/containers/buildah v1.17.0 +# github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2 github.com/containers/buildah github.com/containers/buildah/bind github.com/containers/buildah/chroot @@ -103,7 +105,7 @@ github.com/containers/common/pkg/sysinfo github.com/containers/common/version # github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon/runner/config -# github.com/containers/image/v5 v5.7.0 +# github.com/containers/image/v5 v5.8.0 github.com/containers/image/v5/copy github.com/containers/image/v5/directory github.com/containers/image/v5/directory/explicitfilepath @@ -136,6 +138,7 @@ github.com/containers/image/v5/pkg/compression github.com/containers/image/v5/pkg/compression/internal github.com/containers/image/v5/pkg/compression/types github.com/containers/image/v5/pkg/docker/config +github.com/containers/image/v5/pkg/shortnames github.com/containers/image/v5/pkg/strslice github.com/containers/image/v5/pkg/sysregistriesv2 github.com/containers/image/v5/pkg/tlsclientconfig @@ -334,6 +337,9 @@ github.com/inconshreveable/mousetrap github.com/ishidawataru/sctp # github.com/json-iterator/go v1.1.10 github.com/json-iterator/go +# github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a +github.com/juju/ansiterm +github.com/juju/ansiterm/tabwriter # github.com/klauspost/compress v1.11.2 github.com/klauspost/compress/flate github.com/klauspost/compress/fse @@ -343,6 +349,16 @@ github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd/internal/xxhash # github.com/klauspost/pgzip v1.2.5 github.com/klauspost/pgzip +# github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a +github.com/lunixbochs/vtclean +# github.com/manifoldco/promptui v0.8.0 +github.com/manifoldco/promptui +github.com/manifoldco/promptui/list +github.com/manifoldco/promptui/screenbuf +# github.com/mattn/go-colorable v0.0.9 +github.com/mattn/go-colorable +# github.com/mattn/go-isatty v0.0.4 +github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.9 github.com/mattn/go-runewidth # github.com/mattn/go-shellwords v1.0.10 @@ -497,7 +513,7 @@ github.com/seccomp/libseccomp-golang # github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus github.com/sirupsen/logrus/hooks/syslog -# github.com/spf13/cobra v1.1.1 +# github.com/spf13/cobra v1.1.1 => github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706 github.com/spf13/cobra # github.com/spf13/pflag v1.0.5 github.com/spf13/pflag @@ -570,7 +586,7 @@ go.opencensus.io/internal go.opencensus.io/trace go.opencensus.io/trace/internal go.opencensus.io/trace/tracestate -# go.uber.org/atomic v1.4.0 +# go.uber.org/atomic v1.7.0 go.uber.org/atomic # golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 golang.org/x/crypto/blowfish |