Files
podman/cmd/podman/common/completion.go
Paul Holzinger 370e1132ce completion: fix panic in simplePathJoinUnix()
When we do path completion in images a user could try to complete a
simple relative path, e.g. podman run $IMAGE e... should complete to etc
if this path exists in the image. Right now we panic in this case as the
current check didn't account for an empty string in simplePathJoinUnix().
In such a case return the path directly because we can not alter what
the user typed on the cli and must return a path without slash as well
in order for the shell to suggest the completion.

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=2209809

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2023-05-30 16:09:19 +02:00

1669 lines
66 KiB
Go

package common
import (
"bufio"
"fmt"
"os"
"path"
"reflect"
"strconv"
"strings"
"unicode"
libimageDefine "github.com/containers/common/libimage/define"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/ssh"
"github.com/containers/image/v5/pkg/sysregistriesv2"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/signal"
systemdDefine "github.com/containers/podman/v4/pkg/systemd/define"
"github.com/containers/podman/v4/pkg/util"
securejoin "github.com/cyphar/filepath-securejoin"
"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{"trace", "debug", "info", "warn", "warning", "error", "fatal", "panic"}
// ValidSaveFormats is the list of support podman save formats
ValidSaveFormats = []string{define.OCIManifestDir, define.OCIArchive, define.V2s2ManifestDir, define.V2s2Archive}
)
type completeType int
const (
// complete names and IDs after two chars
completeDefault completeType = iota
// only complete IDs
completeIDs
// only complete Names
completeNames
)
type keyValueCompletion map[string]func(s string) ([]string, cobra.ShellCompDirective)
func setupContainerEngine(cmd *cobra.Command) (entities.ContainerEngine, error) {
containerEngine, err := registry.NewContainerEngine(cmd, []string{})
if err != nil {
cobra.CompErrorln(err.Error())
return nil, err
}
if !registry.IsRemote() {
_, noMoveProcess := cmd.Annotations[registry.NoMoveProcess]
err := containerEngine.SetupRootless(registry.Context(), noMoveProcess)
if err != nil {
return nil, err
}
}
return containerEngine, nil
}
func setupImageEngine(cmd *cobra.Command) (entities.ImageEngine, error) {
imageEngine, err := registry.NewImageEngine(cmd, []string{})
if err != nil {
return nil, err
}
// we also need to set up the container engine since this
// is required to set up the rootless namespace
if _, err = setupContainerEngine(cmd); err != nil {
return nil, err
}
return imageEngine, nil
}
func getContainers(cmd *cobra.Command, toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOpts := entities.ContainerListOptions{
Filters: make(map[string][]string),
}
listOpts.All = true
listOpts.Pod = true
if len(statuses) > 0 {
listOpts.Filters["status"] = statuses
}
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
containers, err := engine.ContainerList(registry.GetContext(), listOpts)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for _, c := range containers {
// include ids in suggestions if cType == completeIDs or
// more then 2 chars are typed and cType == completeDefault
if ((len(toComplete) > 1 && cType == completeDefault) ||
cType == completeIDs) && strings.HasPrefix(c.ID, toComplete) {
suggestions = append(suggestions, c.ID[0:12]+"\t"+c.PodName)
}
// include name in suggestions
if cType != completeIDs && strings.HasPrefix(c.Names[0], toComplete) {
suggestions = append(suggestions, c.Names[0]+"\t"+c.PodName)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func getPods(cmd *cobra.Command, toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOpts := entities.PodPSOptions{
Filters: make(map[string][]string),
}
if len(statuses) > 0 {
listOpts.Filters["status"] = statuses
}
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
pods, err := engine.PodPs(registry.GetContext(), listOpts)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for _, pod := range pods {
// include ids in suggestions if cType == completeIDs or
// more then 2 chars are typed and cType == completeDefault
if ((len(toComplete) > 1 && cType == completeDefault) ||
cType == completeIDs) && strings.HasPrefix(pod.Id, toComplete) {
suggestions = append(suggestions, pod.Id[0:12])
}
// include name in suggestions
if cType != completeIDs && strings.HasPrefix(pod.Name, toComplete) {
suggestions = append(suggestions, pod.Name)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func getVolumes(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
lsOpts := entities.VolumeListOptions{}
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
volumes, err := engine.VolumeList(registry.GetContext(), lsOpts)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for _, v := range volumes {
if strings.HasPrefix(v.Name, toComplete) {
suggestions = append(suggestions, v.Name)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func getImages(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOptions := entities.ImageListOptions{}
engine, err := setupImageEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
images, err := engine.List(registry.GetContext(), listOptions)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for _, image := range images {
// include ids in suggestions if more then 2 chars are typed
if len(toComplete) > 1 && strings.HasPrefix(image.ID, toComplete) {
suggestions = append(suggestions, image.ID[0:12])
}
for _, repo := range image.RepoTags {
if toComplete == "" {
// suggest only full repo path if no input is given
if strings.HasPrefix(repo, toComplete) {
suggestions = append(suggestions, repo)
}
} else {
// suggested "registry.fedoraproject.org/f29/httpd:latest" as
// - "registry.fedoraproject.org/f29/httpd:latest"
// - "f29/httpd:latest"
// - "httpd:latest"
paths := strings.Split(repo, "/")
for i := range paths {
suggestionWithTag := strings.Join(paths[i:], "/")
if strings.HasPrefix(suggestionWithTag, toComplete) {
suggestions = append(suggestions, suggestionWithTag)
}
}
}
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func getSecrets(cmd *cobra.Command, toComplete string, cType completeType) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
secrets, err := engine.SecretList(registry.GetContext(), entities.SecretListRequest{})
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for _, s := range secrets {
// works the same as in getNetworks
if ((len(toComplete) > 1 && cType == completeDefault) ||
cType == completeIDs) && strings.HasPrefix(s.ID, toComplete) {
suggestions = append(suggestions, s.ID[0:12])
}
// include name in suggestions
if cType != completeIDs && strings.HasPrefix(s.Spec.Name, toComplete) {
suggestions = append(suggestions, s.Spec.Name)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func getRegistries() ([]string, cobra.ShellCompDirective) {
regs, err := sysregistriesv2.UnqualifiedSearchRegistries(nil)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
return regs, cobra.ShellCompDirectiveNoFileComp
}
func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
networkListOptions := entities.NetworkListOptions{}
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
networks, err := engine.NetworkList(registry.Context(), networkListOptions)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for _, n := range networks {
// include ids in suggestions if cType == completeIDs or
// more then 2 chars are typed and cType == completeDefault
if ((len(toComplete) > 1 && cType == completeDefault) ||
cType == completeIDs) && strings.HasPrefix(n.ID, toComplete) {
suggestions = append(suggestions, n.ID[0:12])
}
// include name in suggestions
if cType != completeIDs && strings.HasPrefix(n.Name, toComplete) {
suggestions = append(suggestions, n.Name)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func fdIsNotDir(f *os.File) bool {
stat, err := f.Stat()
if err != nil {
cobra.CompErrorln(err.Error())
return true
}
return !stat.IsDir()
}
func getPathCompletion(root string, toComplete string) ([]string, cobra.ShellCompDirective) {
if toComplete == "" {
toComplete = "/"
}
// Important: securejoin is required to make sure we never leave the root mount point
userpath, err := securejoin.SecureJoin(root, toComplete)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveDefault
}
var base string
f, err := os.Open(userpath)
// when error or file is not dir get the parent path to stat
if err != nil || fdIsNotDir(f) {
// Do not use path.Dir() since this cleans the paths which
// then no longer matches the user input.
userpath, base = path.Split(userpath)
toComplete, _ = path.Split(toComplete)
f, err = os.Open(userpath)
if err != nil {
return nil, cobra.ShellCompDirectiveDefault
}
}
if fdIsNotDir(f) {
// nothing to complete since it is no dir
return nil, cobra.ShellCompDirectiveDefault
}
entries, err := f.ReadDir(-1)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveDefault
}
if len(entries) == 0 {
// path is empty dir, just add the trailing slash and no space
if !strings.HasSuffix(toComplete, "/") {
toComplete += "/"
}
return []string{toComplete}, cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace
}
completions := make([]string, 0, len(entries))
count := 0
for _, e := range entries {
if strings.HasPrefix(e.Name(), base) {
suf := ""
// When the entry is a directory we add the "/" as suffix and do not want to add space
// to match normal shell completion behavior.
// Just inc counter again to fake more than one entry in this case and thus get no space.
if e.IsDir() {
suf = "/"
count++
}
completions = append(completions, simplePathJoinUnix(toComplete, e.Name()+suf))
count++
}
}
directive := cobra.ShellCompDirectiveDefault
if count > 1 {
// when we have more than one match we do not want to add a space after the completion
directive |= cobra.ShellCompDirectiveNoSpace
}
return completions, directive
}
// simplePathJoinUnix joins to path components by adding a slash only if p1 doesn't end with one.
// We cannot use path.Join() for the completions logic because this one always calls Clean() on
// the path which changes it from the input.
func simplePathJoinUnix(p1, p2 string) string {
if len(p1) == 0 {
// Special case if p1 is not set just return p2 as is
// and do not add a slash as the input didn't contain one either.
return p2
}
if p1[len(p1)-1] == '/' {
return p1 + p2
}
return p1 + "/" + p2
}
// validCurrentCmdLine validates the current cmd line
// It utilizes the Args function from the cmd struct
// In most cases the Args function validates the args length but it
// is also used to verify that --latest is not given with an argument.
// This function helps to makes sure we only complete valid arguments.
func validCurrentCmdLine(cmd *cobra.Command, args []string, toComplete string) bool {
if cmd.Args == nil {
// Without an Args function we cannot check so assume it's correct
return true
}
// We have to append toComplete to the args otherwise the
// argument count would not match the expected behavior
if err := cmd.Args(cmd, append(args, toComplete)); err != nil {
// Special case if we use ExactArgs(2) or MinimumNArgs(2),
// They will error if we try to complete the first arg.
// Let's try to parse the common error and compare if we have less args than
// required. In this case we are fine and should provide completion.
// Clean the err msg so we can parse it with fmt.Sscanf
// Trim MinimumNArgs prefix
cleanErr := strings.TrimPrefix(err.Error(), "requires at least ")
// Trim MinimumNArgs "only" part
cleanErr = strings.ReplaceAll(cleanErr, "only received", "received")
// Trim ExactArgs prefix
cleanErr = strings.TrimPrefix(cleanErr, "accepts ")
var need, got int
cobra.CompDebugln(cleanErr, true)
_, err = fmt.Sscanf(cleanErr, "%d arg(s), received %d", &need, &got)
if err == nil {
if need >= got {
// We still need more arguments so provide more completions
return true
}
}
return false
}
return true
}
func prefixSlice(pre string, slice []string) []string {
for i := range slice {
slice[i] = pre + slice[i]
}
return slice
}
func suffixCompSlice(suf string, slice []string) []string {
for i := range slice {
split := strings.SplitN(slice[i], "\t", 2)
if len(split) > 1 {
slice[i] = split[0] + suf + "\t" + split[1]
} else {
slice[i] += suf
}
}
return slice
}
func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra.ShellCompDirective) {
suggestions := make([]string, 0, len(k))
directive := cobra.ShellCompDirectiveNoFileComp
for key, getComps := range k {
if strings.HasPrefix(toComplete, key) {
if getComps != nil {
suggestions, dir := getComps(toComplete[len(key):])
return prefixSlice(key, suggestions), dir
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(key, toComplete) {
suggestions = append(suggestions, key)
latKey := key[len(key)-1:]
if latKey == "=" || latKey == ":" {
// make sure we don't add a space after ':' or '='
directive = cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
}
}
}
return suggestions, directive
}
func getBoolCompletion(_ string) ([]string, cobra.ShellCompDirective) {
return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp
}
/* Autocomplete Functions for cobra ValidArgsFunction */
// AutocompleteContainers - Autocomplete all container names.
func AutocompleteContainers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getContainers(cmd, toComplete, completeDefault)
}
// AutocompleteContainersCreated - Autocomplete only created container names.
func AutocompleteContainersCreated(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getContainers(cmd, toComplete, completeDefault, "created")
}
// AutocompleteContainersExited - Autocomplete only exited container names.
func AutocompleteContainersExited(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getContainers(cmd, toComplete, completeDefault, "exited")
}
// AutocompleteContainersPaused - Autocomplete only paused container names.
func AutocompleteContainersPaused(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getContainers(cmd, toComplete, completeDefault, "paused")
}
// AutocompleteContainersRunning - Autocomplete only running container names.
func AutocompleteContainersRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getContainers(cmd, toComplete, completeDefault, "running")
}
// AutocompleteContainersStartable - Autocomplete only created and exited container names.
func AutocompleteContainersStartable(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getContainers(cmd, toComplete, completeDefault, "created", "exited")
}
// AutocompletePods - Autocomplete all pod names.
func AutocompletePods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getPods(cmd, toComplete, completeDefault)
}
// AutocompletePodsRunning - Autocomplete only running pod names.
// It considers degraded as running.
func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getPods(cmd, toComplete, completeDefault, "running", "degraded")
}
// AutoCompletePodsPause - Autocomplete only paused pod names
// When a pod has a few containers paused, that ends up in degraded state
// So autocomplete degraded pod names as well
func AutoCompletePodsPause(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getPods(cmd, toComplete, completeDefault, "paused", "degraded")
}
// AutocompleteForKube - Autocomplete all Podman objects supported by kube generate.
func AutocompleteForKube(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
containers, _ := getContainers(cmd, toComplete, completeDefault)
pods, _ := getPods(cmd, toComplete, completeDefault)
volumes, _ := getVolumes(cmd, toComplete)
objs := containers
objs = append(objs, pods...)
objs = append(objs, volumes...)
return objs, cobra.ShellCompDirectiveNoFileComp
}
func AutocompleteForGenerate(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return AutocompleteForKube(cmd, args, toComplete)
}
// AutocompleteContainersAndPods - Autocomplete container names and pod names.
func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
containers, _ := getContainers(cmd, toComplete, completeDefault)
pods, _ := getPods(cmd, toComplete, completeDefault)
return append(containers, pods...), cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteContainersAndImages - Autocomplete container names and pod names.
func AutocompleteContainersAndImages(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
containers, _ := getContainers(cmd, toComplete, completeDefault)
images, _ := getImages(cmd, toComplete)
return append(containers, images...), cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteVolumes - Autocomplete volumes.
func AutocompleteVolumes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getVolumes(cmd, toComplete)
}
// AutocompleteSecrets - Autocomplete secrets.
func AutocompleteSecrets(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getSecrets(cmd, toComplete, completeDefault)
}
func AutocompleteSecretCreate(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return nil, cobra.ShellCompDirectiveDefault
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteImages - Autocomplete images.
func AutocompleteImages(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getImages(cmd, toComplete)
}
// AutocompleteImageSearchFilters - Autocomplate `search --filter`.
func AutocompleteImageSearchFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return libimageDefine.SearchFilters, cobra.ShellCompDirectiveNoFileComp
}
// AutocompletePodExitPolicy - Autocomplete pod exit policy.
func AutocompletePodExitPolicy(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return config.PodExitPolicies, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteCreateRun - Autocomplete only the fist argument as image and then do file completion.
func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) < 1 {
// check if the rootfs flag is set
// if it is set to true provide directory completion
rootfs, err := cmd.Flags().GetBool("rootfs")
if err == nil && rootfs {
return nil, cobra.ShellCompDirectiveFilterDirs
}
return getImages(cmd, toComplete)
}
// Mount the image and provide path completion
engine, err := setupImageEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveDefault
}
resp, err := engine.Mount(registry.Context(), []string{args[0]}, entities.ImageMountOptions{})
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveDefault
}
defer func() {
_, err := engine.Unmount(registry.Context(), []string{args[0]}, entities.ImageUnmountOptions{})
if err != nil {
cobra.CompErrorln(err.Error())
}
}()
if len(resp) != 1 {
return nil, cobra.ShellCompDirectiveDefault
}
// So this uses ShellCompDirectiveDefault to also still provide normal shell
// completion in case no path matches. This is useful if someone tries to get
// completion for paths that are not available in the image, e.g. /proc/...
return getPathCompletion(resp[0].Path, toComplete)
}
// AutocompleteRegistries - Autocomplete registries.
func AutocompleteRegistries(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getRegistries()
}
// AutocompleteNetworks - Autocomplete networks.
func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getNetworks(cmd, toComplete, completeDefault)
}
// AutocompleteDefaultOneArg - Autocomplete path only for the first argument.
func AutocompleteDefaultOneArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return nil, cobra.ShellCompDirectiveDefault
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteCommitCommand - Autocomplete podman commit command args.
func AutocompleteCommitCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) == 0 {
return getContainers(cmd, toComplete, completeDefault)
}
if len(args) == 1 {
return getImages(cmd, toComplete)
}
// don't complete more than 2 args
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteCpCommand - Autocomplete podman cp command args.
func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) < 2 {
if i := strings.IndexByte(toComplete, ':'); i > -1 {
// Looks like the user already set the container.
// Let's mount it and provide path completion for files in the container.
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveDefault
}
resp, err := engine.ContainerMount(registry.Context(), []string{toComplete[:i]}, entities.ContainerMountOptions{})
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveDefault
}
defer func() {
_, err := engine.ContainerUnmount(registry.Context(), []string{toComplete[:i]}, entities.ContainerUnmountOptions{})
if err != nil {
cobra.CompErrorln(err.Error())
}
}()
if len(resp) != 1 {
return nil, cobra.ShellCompDirectiveDefault
}
comps, directive := getPathCompletion(resp[0].Path, toComplete[i+1:])
return prefixSlice(toComplete[:i+1], comps), directive
}
// Suggest containers when they match the input otherwise normal shell completion is used
containers, _ := getContainers(cmd, toComplete, completeDefault)
for _, container := range containers {
if strings.HasPrefix(container, toComplete) {
return suffixCompSlice(":", containers), cobra.ShellCompDirectiveNoSpace
}
}
// else complete paths on the host
return nil, cobra.ShellCompDirectiveDefault
}
// don't complete more than 2 args
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteExecCommand - Autocomplete podman exec command args.
func AutocompleteExecCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) == 0 {
return getContainers(cmd, toComplete, completeDefault, "running")
}
return nil, cobra.ShellCompDirectiveDefault
}
// AutocompleteRunlabelCommand - Autocomplete podman container runlabel command args.
func AutocompleteRunlabelCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) == 0 {
// This is unfortunate because the argument order is label followed by image.
// If it would be the other way around we could inspect the first arg and get
// all labels from it to suggest them.
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) == 1 {
return getImages(cmd, toComplete)
}
return nil, cobra.ShellCompDirectiveDefault
}
// AutocompleteContainerOneArg - Autocomplete containers as fist arg.
func AutocompleteContainerOneArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) == 0 {
return getContainers(cmd, toComplete, completeDefault)
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteNetworkConnectCmd - Autocomplete podman network connect/disconnect command args.
func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return getNetworks(cmd, toComplete, completeDefault)
}
if len(args) == 1 {
return getContainers(cmd, toComplete, completeDefault)
}
// don't complete more than 2 args
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteTopCmd - Autocomplete podman top/pod top command args.
func AutocompleteTopCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
latest := cmd.Flags().Lookup("latest")
// only complete containers/pods as first arg if latest is not set
if len(args) == 0 && (latest == nil || !latest.Changed) {
if cmd.Parent().Name() == "pod" {
// need to complete pods since we are using pod top
return getPods(cmd, toComplete, completeDefault)
}
return getContainers(cmd, toComplete, completeDefault)
}
descriptors, err := util.GetContainerPidInformationDescriptors()
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
return descriptors, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteInspect - Autocomplete podman inspect.
func AutocompleteInspect(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
containers, _ := getContainers(cmd, toComplete, completeDefault)
images, _ := getImages(cmd, toComplete)
pods, _ := getPods(cmd, toComplete, completeDefault)
networks, _ := getNetworks(cmd, toComplete, completeDefault)
volumes, _ := getVolumes(cmd, toComplete)
suggestions := make([]string, 0, len(containers)+len(images)+len(pods)+len(networks)+len(volumes))
suggestions = append(suggestions, containers...)
suggestions = append(suggestions, images...)
suggestions = append(suggestions, pods...)
suggestions = append(suggestions, networks...)
suggestions = append(suggestions, volumes...)
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteSystemConnections - Autocomplete system connections.
func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
suggestions := []string{}
cfg, err := config.ReadCustomConfig()
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for k, v := range cfg.Engine.ServiceDestinations {
// the URI will be show as description in shells like zsh
suggestions = append(suggestions, k+"\t"+v.URI)
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteScp returns a list of connections, images, or both, depending on the amount of arguments
func AutocompleteScp(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
switch len(args) {
case 0:
split := strings.SplitN(toComplete, "::", 2)
if len(split) > 1 {
imageSuggestions, _ := getImages(cmd, split[1])
return prefixSlice(split[0]+"::", imageSuggestions), cobra.ShellCompDirectiveNoFileComp
}
connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete)
imageSuggestions, _ := getImages(cmd, toComplete)
totalSuggestions := append(suffixCompSlice("::", connectionSuggestions), imageSuggestions...)
directive := cobra.ShellCompDirectiveNoFileComp
// if we have connections do not add a space after the completion
if len(connectionSuggestions) > 0 {
directive = cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
}
return totalSuggestions, directive
case 1:
split := strings.SplitN(args[0], "::", 2)
if len(split) > 1 {
if len(split[1]) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
imageSuggestions, _ := getImages(cmd, toComplete)
return imageSuggestions, cobra.ShellCompDirectiveNoFileComp
}
connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete)
return suffixCompSlice("::", connectionSuggestions), cobra.ShellCompDirectiveNoFileComp
}
return nil, 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
}
// AutocompleteInitCtr - Autocomplete init container type
// -> "once", "always"
func AutocompleteInitCtr(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
InitCtrType := []string{define.AlwaysInitContainer, define.OneShotInitContainer}
return InitCtrType, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteCreateAttach - Autocomplete create --attach options.
// -> "stdin", "stdout", "stderr"
func AutocompleteCreateAttach(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"stdin", "stdout", "stderr"}, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteNamespace - Autocomplete namespace options.
// -> host,container:[name],ns:[path],private
func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"ns:": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault },
"host": nil,
"private": nil,
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteUserNamespace - Autocomplete namespace options.
// -> same as AutocompleteNamespace with "auto", "keep-id" added
func AutocompleteUserNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
results, directive := AutocompleteNamespace(cmd, args, toComplete)
results = append(results, "auto", "keep-id", "nomap")
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", "passthrough"
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}
if !registry.IsRemote() {
logDrivers = append(logDrivers, define.PassthroughLogging)
}
return logDrivers, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteLogOpt - Autocomplete log-opt options.
// -> "path=", "tag="
func AutocompleteLogOpt(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
logOptions := []string{"path=", "tag=", "max-size="}
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", "newer"}
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{define.RestartPolicyAlways, define.RestartPolicyNo,
define.RestartPolicyOnFailure, define.RestartPolicyUnlessStopped}
return restartOptions, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteSecurityOption - Autocomplete security options options.
func AutocompleteSecurityOption(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"apparmor=": nil,
"no-new-privileges": nil,
"seccomp=": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault },
"label=": func(s string) ([]string, cobra.ShellCompDirective) {
if strings.HasPrefix(s, "d") {
return []string{"disable"}, cobra.ShellCompDirectiveNoFileComp
}
return []string{"user:", "role:", "type:", "level:", "filetype:", "disable"}, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
},
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteStopSignal - Autocomplete stop signal options.
// Autocompletes signals both lower or uppercase depending on the user input.
func AutocompleteStopSignal(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// convertCase will convert a string to lowercase only if the user input is lowercase
convertCase := func(s string) string { return s }
if len(toComplete) > 0 && unicode.IsLower(rune(toComplete[0])) {
convertCase = strings.ToLower
}
prefix := ""
// if input starts with "SI" we have to add SIG in front
// since the signal map does not have this prefix but the option
// allows signals with and without SIG prefix
if strings.HasPrefix(toComplete, convertCase("SI")) {
prefix = "SIG"
}
stopSignals := make([]string, 0, len(signal.SignalMap))
for sig := range signal.SignalMap {
stopSignals = append(stopSignals, convertCase(prefix+sig))
}
return stopSignals, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteSystemdFlag - Autocomplete systemd flag options.
// -> "true", "false", "always"
func AutocompleteSystemdFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
systemd := []string{"true", "false", "always"}
return systemd, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteUserFlag - Autocomplete user flag based on the names and groups (includes ids after first char) in /etc/passwd and /etc/group files.
// -> user:group
func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if strings.Contains(toComplete, ":") {
// It would be nice to read the file in the image
// but at this point we don't know the image.
file, err := os.Open("/etc/group")
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
defer file.Close()
var groups []string
scanner := bufio.NewScanner(file)
user := strings.SplitN(toComplete, ":", 2)[0]
for scanner.Scan() {
entries := strings.SplitN(scanner.Text(), ":", 4)
groups = append(groups, user+":"+entries[0])
// complete ids after at least one char is given
if len(user)+1 < len(toComplete) {
groups = append(groups, user+":"+entries[2])
}
}
if err = scanner.Err(); err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
return groups, cobra.ShellCompDirectiveNoFileComp
}
// It would be nice to read the file in the image
// but at this point we don't know the image.
file, err := os.Open("/etc/passwd")
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
defer file.Close()
var users []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
entries := strings.SplitN(scanner.Text(), ":", 7)
users = append(users, entries[0]+":")
// complete ids after at least one char is given
if len(toComplete) > 0 {
users = append(users, entries[2]+":")
}
}
if err = scanner.Err(); err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return users, cobra.ShellCompDirectiveNoSpace
}
// AutocompleteMountFlag - Autocomplete mount flag options.
// -> "type=bind,", "type=volume,", "type=tmpfs,"
func AutocompleteMountFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{"type=bind,", "type=volume,", "type=tmpfs,"}
// TODO: Add support for all different options
return types, cobra.ShellCompDirectiveNoSpace
}
// AutocompleteVolumeFlag - Autocomplete volume flag options.
// -> volumes and paths
func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
volumes, _ := getVolumes(cmd, toComplete)
directive := cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveDefault
if strings.Contains(toComplete, ":") {
// add space after second path
directive = cobra.ShellCompDirectiveDefault
}
return volumes, directive
}
// AutocompleteNetworkFlag - Autocomplete network flag options.
func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"ns:": func(_ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
},
"bridge": nil,
"none": nil,
"host": nil,
"private": nil,
"slirp4netns:": func(s string) ([]string, cobra.ShellCompDirective) {
skv := keyValueCompletion{
"allow_host_loopback=": getBoolCompletion,
"cidr=": nil,
"enable_ipv6=": getBoolCompletion,
"mtu=": nil,
"outbound_addr=": nil,
"outbound_addr6=": nil,
"port_handler=": func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{"rootlesskit", "slirp4netns"}, cobra.ShellCompDirectiveNoFileComp
},
}
return completeKeyValues(s, skv)
},
}
networks, _ := getNetworks(cmd, toComplete, completeDefault)
suggestions, dir := completeKeyValues(toComplete, kv)
// add slirp4netns here it does not work correct if we add it to the kv map
suggestions = append(suggestions, "slirp4netns")
return append(networks, suggestions...), dir
}
type formatSuggestion struct {
fieldname string
suffix string
}
func convertFormatSuggestions(suggestions []formatSuggestion) []string {
completions := make([]string, 0, len(suggestions))
for _, f := range suggestions {
completions = append(completions, f.fieldname+f.suffix)
}
return completions
}
// AutocompleteFormat - Autocomplete json or a given struct to use for a go template.
// The input can be nil, In this case only json will be autocompleted.
// This function will only work for pointer to structs other types are not supported.
// When "{{." is typed the field and method names of the given struct will be completed.
// This also works recursive for nested structs.
func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// this function provides shell completion for go templates
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// autocomplete json when nothing or json is typed
// gocritic complains about the argument order but this is correct in this case
//nolint:gocritic
if strings.HasPrefix("json", toComplete) {
return []string{"json"}, cobra.ShellCompDirectiveNoFileComp
}
// no input struct we cannot provide completion return nothing
if o == nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// toComplete could look like this: {{ .Config }} {{ .Field.F
// 1. split the template variable delimiter
vars := strings.Split(toComplete, "{{")
if len(vars) == 1 {
// no variables return no completion
return nil, cobra.ShellCompDirectiveNoFileComp
}
// clean the spaces from the last var
field := strings.Split(vars[len(vars)-1], " ")
// split this into it struct field names
fields := strings.Split(field[len(field)-1], ".")
f := reflect.ValueOf(o)
if f.Kind() != reflect.Ptr {
// We panic here to make sure that all callers pass the value by reference.
// If someone passes a by value then all podman commands will panic since
// this function is run at init time.
panic("AutocompleteFormat: passed value must be a pointer to a struct")
}
for i := 1; i < len(fields); i++ {
// last field get all names to suggest
if i == len(fields)-1 {
suggestions := getStructFields(f, fields[i])
// add the current toComplete value in front so that the shell can complete this correctly
toCompArr := strings.Split(toComplete, ".")
toCompArr[len(toCompArr)-1] = ""
toComplete = strings.Join(toCompArr, ".")
return prefixSlice(toComplete, convertFormatSuggestions(suggestions)), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
}
// first follow pointer and create element when it is nil
f = actualReflectValue(f)
switch f.Kind() {
case reflect.Struct:
for j := 0; j < f.NumField(); j++ {
field := f.Type().Field(j)
// ok this is a bit weird but when we have an embedded nil struct
// calling FieldByName on a name which is present on this struct will panic
// Therefore we have to init them (non nil ptr), https://github.com/containers/podman/issues/14223
if field.Anonymous && f.Field(j).Type().Kind() == reflect.Ptr {
f.Field(j).Set(reflect.New(f.Field(j).Type().Elem()))
}
}
// set the next struct field
f = f.FieldByName(fields[i])
case reflect.Map:
rtype := f.Type().Elem()
if rtype.Kind() == reflect.Ptr {
rtype = rtype.Elem()
}
f = reflect.New(rtype)
case reflect.Func:
if f.Type().NumOut() != 1 {
// unsupported type return nothing
return nil, cobra.ShellCompDirectiveNoFileComp
}
f = reflect.New(f.Type().Out(0))
default:
// unsupported type return nothing
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
// actualReflectValue takes the value,
// if it is pointer it will dereference it and when it is nil,
// it will create a new value from it
func actualReflectValue(f reflect.Value) reflect.Value {
// follow the pointer first
if f.Kind() == reflect.Ptr {
// if the pointer is nil we create a new value from the elements type
// this allows us to follow nil pointers and get the actual type
if f.IsNil() {
f = reflect.New(f.Type().Elem())
}
f = f.Elem()
}
return f
}
// getStructFields reads all struct field names and method names and returns them.
func getStructFields(f reflect.Value, prefix string) []formatSuggestion {
var suggestions []formatSuggestion
if f.IsValid() {
suggestions = append(suggestions, getMethodNames(f, prefix)...)
}
f = actualReflectValue(f)
// we only support structs
if f.Kind() != reflect.Struct {
return suggestions
}
var anonymous []formatSuggestion
// loop over all field names
for j := 0; j < f.NumField(); j++ {
field := f.Type().Field(j)
// check if struct field is not exported, templates only use exported fields
// PkgPath is always empty for exported fields
if field.PkgPath != "" {
continue
}
fname := field.Name
suffix := "}}"
kind := field.Type.Kind()
if kind == reflect.Ptr {
// make sure to read the actual type when it is a pointer
kind = field.Type.Elem().Kind()
}
// when we have a nested struct do not append braces instead append a dot
if kind == reflect.Struct || kind == reflect.Map {
suffix = "."
}
// if field is anonymous add the child fields as well
if field.Anonymous {
anonymous = append(anonymous, getStructFields(f.Field(j), prefix)...)
}
if strings.HasPrefix(fname, prefix) {
// add field name with suffix
suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix})
}
}
outer:
for _, ano := range anonymous {
// we should only add anonymous child fields if they are not already present.
for _, sug := range suggestions {
if ano.fieldname == sug.fieldname {
continue outer
}
}
suggestions = append(suggestions, ano)
}
return suggestions
}
func getMethodNames(f reflect.Value, prefix string) []formatSuggestion {
suggestions := make([]formatSuggestion, 0, f.NumMethod())
for j := 0; j < f.NumMethod(); j++ {
method := f.Type().Method(j)
// in a template we can only run functions with one return value
if method.Func.Type().NumOut() != 1 {
continue
}
// when we have a nested struct do not append braces instead append a dot
kind := method.Func.Type().Out(0).Kind()
suffix := "}}"
if kind == reflect.Struct || kind == reflect.Map {
suffix = "."
}
// From a template user's POV it is not important whether they use a struct field or method.
// They only notice the difference when the function requires arguments.
// So let's be nice and let the user know that this method requires arguments via the help text.
// Note since this is actually a method on a type the first argument is always fix so we should skip it.
num := method.Func.Type().NumIn() - 1
if num > 0 {
// everything after tab will the completion scripts show as help when enabled
// overwrite the suffix because it expects the args
suffix = "\tThis is a function and requires " + strconv.Itoa(num) + " argument"
if num > 1 {
// add plural s
suffix += "s"
}
}
fname := method.Name
if strings.HasPrefix(fname, prefix) {
// add method name with closing braces
suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix})
}
}
return suggestions
}
// AutocompleteEventFilter - Autocomplete event filter flag options.
// -> "container=", "event=", "image=", "pod=", "volume=", "type="
func AutocompleteEventFilter(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
event := func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{events.Attach.String(), events.AutoUpdate.String(), events.Checkpoint.String(), events.Cleanup.String(),
events.Commit.String(), events.Create.String(), events.Exec.String(), events.ExecDied.String(),
events.Exited.String(), events.Export.String(), events.Import.String(), events.Init.String(), events.Kill.String(),
events.LoadFromArchive.String(), events.Mount.String(), events.NetworkConnect.String(),
events.NetworkDisconnect.String(), events.Pause.String(), events.Prune.String(), events.Pull.String(),
events.Push.String(), events.Refresh.String(), events.Remove.String(), events.Rename.String(),
events.Renumber.String(), events.Restart.String(), events.Restore.String(), events.Save.String(),
events.Start.String(), events.Stop.String(), events.Sync.String(), events.Tag.String(), events.Unmount.String(),
events.Unpause.String(), events.Untag.String(),
}, cobra.ShellCompDirectiveNoFileComp
}
eventTypes := func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{events.Container.String(), events.Image.String(), events.Network.String(),
events.Pod.String(), events.System.String(), events.Volume.String(),
}, cobra.ShellCompDirectiveNoFileComp
}
kv := keyValueCompletion{
"container=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"image=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
"pod=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeDefault) },
"volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
"event=": event,
"type=": eventTypes,
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteSystemdRestartOptions - Autocomplete systemd restart options.
// -> "no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"
func AutocompleteSystemdRestartOptions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return systemdDefine.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.
func AutocompleteInspectType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{AllType, ContainerType, ImageType, NetworkType, PodType, VolumeType}
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.
func AutocompleteNetworkDriver(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
info, err := engine.Info(registry.Context())
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
return info.Plugins.Network, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteNetworkIPAMDriver - Autocomplete network ipam driver option.
// -> "bridge", "macvlan"
func AutocompleteNetworkIPAMDriver(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
drivers := []string{types.HostLocalIPAMDriver, types.DHCPIPAMDriver, types.NoneIPAMDriver}
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"}
split := strings.Split(toComplete, ",")
split[len(split)-1] = ""
toComplete = strings.Join(split, ",")
return prefixSlice(toComplete, 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.
func AutocompleteImageSaveFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return ValidSaveFormats, 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{config.CgroupfsCgroupsManager, config.SystemdCgroupsManager}
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{events.LogFile.String(), events.Journald.String(), events.Null.String()}
return types, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteNetworkBackend - Autocomplete network backend options.
// -> "cni", "netavark"
func AutocompleteNetworkBackend(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{string(types.CNI), string(types.Netavark)}
return types, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteLogLevel - Autocomplete log level options.
// -> "trace", "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{define.SdNotifyModeContainer, define.SdNotifyModeContainer, define.SdNotifyModeIgnore}
return types, cobra.ShellCompDirectiveNoFileComp
}
var containerStatuses = []string{"created", "running", "paused", "stopped", "exited", "unknown"}
// AutocompletePsFilters - Autocomplete ps filter options.
func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"id=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeIDs) },
"name=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) },
"status=": func(_ string) ([]string, cobra.ShellCompDirective) {
return containerStatuses, cobra.ShellCompDirectiveNoFileComp
},
"ancestor=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
"before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
"health=": func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{define.HealthCheckHealthy,
define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp
},
"network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) },
"label=": nil,
"exited=": nil,
"until=": nil,
}
return completeKeyValues(toComplete, kv)
}
// AutocompletePodPsFilters - Autocomplete pod ps filter options.
func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"id=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeIDs) },
"name=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeNames) },
"status=": func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{"stopped", "running",
"paused", "exited", "dead", "created", "degraded"}, cobra.ShellCompDirectiveNoFileComp
},
"ctr-ids=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeIDs) },
"ctr-names=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) },
"ctr-number=": nil,
"ctr-status=": func(_ string) ([]string, cobra.ShellCompDirective) {
return containerStatuses, cobra.ShellCompDirectiveNoFileComp
},
"network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) },
"label=": nil,
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteImageFilters - Autocomplete image ls --filter options.
func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
getImg := func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) }
kv := keyValueCompletion{
"before=": getImg,
"since=": getImg,
"label=": nil,
"reference=": nil,
"dangling=": getBoolCompletion,
"readonly=": getBoolCompletion,
}
return completeKeyValues(toComplete, kv)
}
// AutocompletePruneFilters - Autocomplete container/image prune --filter options.
func AutocompletePruneFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"until=": nil,
"label=": nil,
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteNetworkFilters - Autocomplete network ls --filter options.
func AutocompleteNetworkFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeNames) },
"id=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeIDs) },
"label=": nil,
"driver=": func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver, types.IPVLANNetworkDriver}, cobra.ShellCompDirectiveNoFileComp
},
"until=": nil,
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteVolumeFilters - Autocomplete volume ls --filter options.
func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
local := func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
}
kv := keyValueCompletion{
"name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
"driver=": local,
"scope=": local,
"label=": nil,
"opt=": nil,
"dangling=": getBoolCompletion,
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteSecretFilters - Autocomplete secret ls --filter options.
func AutocompleteSecretFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kv := keyValueCompletion{
"name=": func(s string) ([]string, cobra.ShellCompDirective) { return getSecrets(cmd, s, completeNames) },
"id=": func(s string) ([]string, cobra.ShellCompDirective) { return getSecrets(cmd, s, completeIDs) },
}
return completeKeyValues(toComplete, kv)
}
// AutocompleteCheckpointCompressType - Autocomplete checkpoint compress type options.
// -> "gzip", "none", "zstd"
func AutocompleteCheckpointCompressType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{"gzip", "none", "zstd"}
return types, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteCompressionFormat - Autocomplete compression-format type options.
func AutocompleteCompressionFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{"gzip", "zstd", "zstd:chunked"}
return types, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteClone - Autocomplete container and image names
func AutocompleteClone(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
switch len(args) {
case 0:
if cmd.Parent().Name() == "pod" { // needs to be " pod " to exclude 'podman'
return getPods(cmd, toComplete, completeDefault)
}
return getContainers(cmd, toComplete, completeDefault)
case 2:
if cmd.Parent().Name() == "pod" {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getImages(cmd, toComplete)
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteSSH - Autocomplete ssh modes
func AutocompleteSSH(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return []string{string(ssh.GolangMode), string(ssh.NativeMode)}, cobra.ShellCompDirectiveNoFileComp
}
// AutocompleteHealthOnFailure - action to take once the container turns unhealthy.
func AutocompleteHealthOnFailure(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return define.SupportedHealthCheckOnFailureActions, cobra.ShellCompDirectiveNoFileComp
}