diff --git a/Makefile b/Makefile index 8785afc208..0316ea2845 100644 --- a/Makefile +++ b/Makefile @@ -475,15 +475,6 @@ 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 @@ -673,14 +664,6 @@ API.md: pkg/varlink/io.podman.varlink .PHONY: validate.completions 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 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a1027b4657..a819fcac2b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,31 @@ # Release Notes +## 2.2.1 +### Changes +- Due to a conflict with a previously-removed field, we were forced to modify the way image volumes (mounting images into containers using `--mount type=image`) were handled in the database. As a result, containers created in Podman 2.2.0 with image volumes will not have them in v2.2.1, and these containers will need to be re-created. + +### Bugfixes +- Fixed a bug where rootless Podman would, on systems without the `XDG_RUNTIME_DIR` environment variable defined, use an incorrect path for the PID file of the Podman pause process, causing Podman to fail to start ([#8539](https://github.com/containers/podman/issues/8539)). +- Fixed a bug where containers created using Podman v1.7 and earlier were unusable in Podman due to JSON decode errors ([#8613](https://github.com/containers/podman/issues/8613)). +- Fixed a bug where Podman could retrieve invalid cgroup paths, instead of erroring, for containers that were not running. +- Fixed a bug where the `podman system reset` command would print a warning about a duplicate shutdown handler being registered. +- Fixed a bug where rootless Podman would attempt to mount `sysfs` in circumstances where it was not allowed; some OCI runtimes (notably `crun`) would fall back to alternatives and not fail, but others (notably `runc`) would fail to run containers. +- Fixed a bug where the `podman run` and `podman create` commands would fail to create containers from untagged images ([#8558](https://github.com/containers/podman/issues/8558)). +- Fixed a bug where remote Podman would prompt for a password even when the server did not support password authentication ([#8498](https://github.com/containers/podman/issues/8498)). +- Fixed a bug where the `podman exec` command did not move the Conmon process for the exec session into the correct cgroup. +- Fixed a bug where shell completion for the `ancestor` option to `podman ps --filter` did not work correctly. +- Fixed a bug where detached containers would not properly clean themselves up (or remove themselves if `--rm` was set) if the Podman command that created them was invoked with `--log-level=debug`. + +### API +- Fixed a bug where the Compat Create endpoint for Containers did not properly handle the `Binds` and `Mounts` parameters in `HostConfig`. +- Fixed a bug where the Compat Create endpoint for Containers ignored the `Name` query parameter. +- Fixed a bug where the Compat Create endpoint for Containers did not properly handle the "default" value for `NetworkMode` (this value is used extensively by `docker-compose`) ([#8544](https://github.com/containers/podman/issues/8544)). +- Fixed a bug where the Compat Build endpoint for Images would sometimes incorrectly use the `target` query parameter as the image's tag. + +### Misc +- Podman v2.2.0 vendored a non-released, custom version of the `github.com/spf13/cobra` package; this has been reverted to the latest upstream release to aid in packaging. +- Updated the containers/image library to v5.9.0 + ## 2.2.0 ### Features - Experimental support for shortname aliasing has been added. This is not enabled by default, but can be turned on by setting the environment variable `CONTAINERS_SHORT_NAME_ALIASING` to `on`. Documentation is [available here](https://github.com/containers/image/blob/master/docs/containers-registries.conf.5.md#short-name-aliasing). diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 9856e46efa..7fed15e5e3 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -861,10 +861,10 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) "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) }, + "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 diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 6dc43dbc63..a20ac9d786 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -203,20 +203,14 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup for _, m := range cc.HostConfig.Mounts { mount := fmt.Sprintf("type=%s", m.Type) if len(m.Source) > 0 { - mount += fmt.Sprintf("source=%s", m.Source) + mount += fmt.Sprintf(",source=%s", m.Source) } if len(m.Target) > 0 { - mount += fmt.Sprintf("dest=%s", m.Target) + mount += fmt.Sprintf(",dst=%s", m.Target) } mounts = append(mounts, mount) } - // volumes - volumes := make([]string, 0, len(cc.Config.Volumes)) - for v := range cc.Config.Volumes { - volumes = append(volumes, v) - } - // dns dns := make([]net.IP, 0, len(cc.HostConfig.DNS)) for _, d := range cc.HostConfig.DNS { @@ -373,7 +367,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup UserNS: string(cc.HostConfig.UsernsMode), UTS: string(cc.HostConfig.UTSMode), Mount: mounts, - Volume: volumes, VolumesFrom: cc.HostConfig.VolumesFrom, Workdir: cc.Config.WorkingDir, Net: &netInfo, @@ -388,6 +381,10 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup } } + // volumes + if volumes := cc.HostConfig.Binds; len(volumes) > 0 { + cliOpts.Volume = volumes + } if len(cc.HostConfig.BlkioWeightDevice) > 0 { devices := make([]string, 0, len(cc.HostConfig.BlkioWeightDevice)) for _, d := range cc.HostConfig.BlkioWeightDevice { diff --git a/cmd/podman/completion/completion.go b/cmd/podman/completion/completion.go index ead8d1f05b..84942a5086 100644 --- a/cmd/podman/completion/completion.go +++ b/cmd/podman/completion/completion.go @@ -67,11 +67,7 @@ func completion(cmd *cobra.Command, args []string) error { var err error switch args[0] { case "bash": - if noDesc { - err = cmd.Root().GenBashCompletion(w) - } else { - err = cmd.Root().GenBashCompletionWithDesc(w) - } + err = cmd.Root().GenBashCompletion(w) case "zsh": if noDesc { err = cmd.Root().GenZshCompletionNoDesc(w) diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index bcb31e6ee1..8a79519236 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -126,8 +126,8 @@ func images(cmd *cobra.Command, args []string) error { case listFlag.quiet: return writeID(imgs) default: - if cmd.Flag("format").Changed { - listFlag.noHeading = true // V1 compatibility + if cmd.Flags().Changed("format") && !parse.HasTable(listFlag.format) { + listFlag.noHeading = true } return writeTemplate(imgs) } diff --git a/docs/source/markdown/podman-network-connect.1.md b/docs/source/markdown/podman-network-connect.1.md index 58b6e5c44f..a31a415dc0 100644 --- a/docs/source/markdown/podman-network-connect.1.md +++ b/docs/source/markdown/podman-network-connect.1.md @@ -10,6 +10,8 @@ podman\-network\-connect - Connect a container to a network Connects a container to a network. A container can be connected to a network by name or by ID. Once connected, the container can communicate with other containers in the same network. +This command is not available for rootless users. + ## OPTIONS #### **--alias** Add network-scoped alias for the container. If the network is using the `dnsname` CNI plugin, these aliases diff --git a/docs/source/markdown/podman-network-disconnect.1.md b/docs/source/markdown/podman-network-disconnect.1.md index 95c7018a8b..8b7125282f 100644 --- a/docs/source/markdown/podman-network-disconnect.1.md +++ b/docs/source/markdown/podman-network-disconnect.1.md @@ -9,6 +9,8 @@ podman\-network\-disconnect - Disconnect a container from a network ## DESCRIPTION Disconnects a container from a network. +This command is not available for rootless users. + ## OPTIONS #### **--force**, **-f** diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md index 47d647b3f6..56515d0c10 100644 --- a/docs/source/markdown/podman-network-inspect.1.md +++ b/docs/source/markdown/podman-network-inspect.1.md @@ -7,7 +7,7 @@ podman\-network\-inspect - Displays the raw CNI network configuration for one or **podman network inspect** [*options*] [*network* ...] ## DESCRIPTION -Display the raw (JSON format) network configuration. This command is not available for rootless users. +Display the raw (JSON format) network configuration. ## OPTIONS #### **--format**, **-f** diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md index 34b40b3ae4..63dcace232 100644 --- a/docs/source/markdown/podman-network-ls.1.md +++ b/docs/source/markdown/podman-network-ls.1.md @@ -7,7 +7,7 @@ podman\-network\-ls - Display a summary of CNI networks **podman network ls** [*options*] ## DESCRIPTION -Displays a list of existing podman networks. This command is not available for rootless users. +Displays a list of existing podman networks. ## OPTIONS #### **--quiet**, **-q** diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md index d21b200d9b..bc161659af 100644 --- a/docs/source/markdown/podman-network.1.md +++ b/docs/source/markdown/podman-network.1.md @@ -7,7 +7,7 @@ podman\-network - Manage Podman CNI networks **podman network** *subcommand* ## DESCRIPTION -The network command manages CNI networks for Podman. It is not supported for rootless users. +The network command manages CNI networks for Podman. ## COMMANDS diff --git a/go.mod b/go.mod index 2ed1c56d1d..a9e2fe5b74 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/containers/buildah v1.18.0 github.com/containers/common v0.29.0 github.com/containers/conmon v2.0.20+incompatible - github.com/containers/image/v5 v5.8.1 + github.com/containers/image/v5 v5.9.0 github.com/containers/psgo v1.5.1 github.com/containers/storage v1.24.1 github.com/coreos/go-systemd/v22 v22.1.0 @@ -75,5 +75,3 @@ 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 diff --git a/go.sum b/go.sum index 761ba04de3..c0fb963e50 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,6 @@ 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= @@ -105,6 +103,8 @@ github.com/containers/image/v5 v5.8.0 h1:B3FGHi0bdGXgg698kBIGOlHCXN5n+scJr6/5354 github.com/containers/image/v5 v5.8.0/go.mod h1:jKxdRtyIDumVa56hdsZvV+gwx4zB50hRou6pIuCWLkg= github.com/containers/image/v5 v5.8.1 h1:aHW8a/Kd0dTJ7PTL/fc6y12sJqHxWgqilu+XyHfjD8Q= github.com/containers/image/v5 v5.8.1/go.mod h1:blOEFd/iFdeyh891ByhCVUc+xAcaI3gBegXECwz9UbQ= +github.com/containers/image/v5 v5.9.0 h1:dRmUtcluQcmasNo3DpnRoZjfU0rOu1qZeL6wlDJr10Q= +github.com/containers/image/v5 v5.9.0/go.mod h1:blOEFd/iFdeyh891ByhCVUc+xAcaI3gBegXECwz9UbQ= 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= @@ -510,6 +510,9 @@ 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= diff --git a/libpod/container.go b/libpod/container.go index e954d84ebd..c86625a4a9 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -916,13 +916,33 @@ func (c *Container) CgroupManager() string { return cgroupManager } -// CGroupPath returns a cgroups "path" for a given container. +// CGroupPath returns a cgroups "path" for the given container. +// Note that the container must be running. Otherwise, an error +// is returned. func (c *Container) CGroupPath() (string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return "", errors.Wrapf(err, "error updating container %s state", c.ID()) + } + } + return c.cGroupPath() +} + +// cGroupPath returns a cgroups "path" for the given container. +// Note that the container must be running. Otherwise, an error +// is returned. +// NOTE: only call this when owning the container's lock. +func (c *Container) cGroupPath() (string, error) { if c.config.NoCgroups || c.config.CgroupsMode == "disabled" { return "", errors.Wrapf(define.ErrNoCgroups, "this container is not creating cgroups") } + if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused { + return "", errors.Wrapf(define.ErrCtrStopped, "cannot get cgroup path unless container %s is running", c.ID()) + } - // Read /proc/[PID]/cgroup and find the *longest* cgroup entry. That's + // Read /proc/{PID}/cgroup and find the *longest* cgroup entry. That's // needed to account for hacks in cgroups v1, where each line in the // file could potentially point to a cgroup. The longest one, however, // is the libpod-specific one we're looking for. @@ -947,7 +967,6 @@ func (c *Container) CGroupPath() (string, error) { if len(path) > len(cgroupPath) { cgroupPath = path } - } if len(cgroupPath) == 0 { diff --git a/libpod/container_config.go b/libpod/container_config.go index cc3ad25ea9..c95be9b55d 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -135,7 +135,13 @@ type ContainerRootFSConfig struct { // OverlayVolumes lists the overlay volumes to mount into the container. OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` // ImageVolumes lists the image volumes to mount into the container. - ImageVolumes []*ContainerImageVolume `json:"imageVolumes,omitempty"` + // Please note that this is named ctrImageVolumes in JSON to + // distinguish between these and the old `imageVolumes` field in Podman + // pre-1.8, which was used in very old Podman versions to determine how + // image volumes were handled in Libpod (support for these eventually + // moved out of Libpod into pkg/specgen). + // Please DO NOT re-use the `imageVolumes` name in container JSON again. + ImageVolumes []*ContainerImageVolume `json:"ctrImageVolumes,omitempty"` // CreateWorkingDir indicates that Libpod should create the container's // working directory if it does not exist. Some OCI runtimes do this by // default, but others do not. diff --git a/libpod/reset.go b/libpod/reset.go index f8828fed4a..6d28427232 100644 --- a/libpod/reset.go +++ b/libpod/reset.go @@ -46,7 +46,7 @@ func (r *Runtime) Reset(ctx context.Context) error { } } - if err := stopPauseProcess(); err != nil { + if err := r.stopPauseProcess(); err != nil { logrus.Errorf("Error stopping pause process: %v", err) } diff --git a/libpod/runtime.go b/libpod/runtime.go index df3dfae2bd..1f50f34075 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -190,7 +190,7 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R if err := shutdown.Register("libpod", func(sig os.Signal) error { os.Exit(1) return nil - }); err != nil { + }); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists { logrus.Errorf("Error registering shutdown handler for libpod: %v", err) } @@ -472,7 +472,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // we will need to access the storage. if os.Geteuid() != 0 { aliveLock.Unlock() // Unlock to avoid deadlock as BecomeRootInUserNS will reexec. - pausePid, err := util.GetRootlessPauseProcessPidPath() + pausePid, err := util.GetRootlessPauseProcessPidPathGivenDir(runtime.config.Engine.TmpDir) if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } @@ -538,6 +538,15 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { return nil } +// TmpDir gets the current Libpod temporary files directory. +func (r *Runtime) TmpDir() (string, error) { + if !r.valid { + return "", define.ErrRuntimeStopped + } + + return r.config.Engine.TmpDir, nil +} + // GetConfig returns a copy of the configuration used by the runtime func (r *Runtime) GetConfig() (*config.Config, error) { r.lock.RLock() diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go index 1ad32fe9cf..f0f800ef0c 100644 --- a/libpod/runtime_migrate.go +++ b/libpod/runtime_migrate.go @@ -18,9 +18,9 @@ import ( "github.com/sirupsen/logrus" ) -func stopPauseProcess() error { +func (r *Runtime) stopPauseProcess() error { if rootless.IsRootless() { - pausePidPath, err := util.GetRootlessPauseProcessPidPath() + pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(r.config.Engine.TmpDir) if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } @@ -98,5 +98,5 @@ func (r *Runtime) migrate(ctx context.Context) error { } } - return stopPauseProcess() + return r.stopPauseProcess() } diff --git a/libpod/runtime_migrate_unsupported.go b/libpod/runtime_migrate_unsupported.go index e362cca637..a9d3513186 100644 --- a/libpod/runtime_migrate_unsupported.go +++ b/libpod/runtime_migrate_unsupported.go @@ -10,6 +10,6 @@ func (r *Runtime) migrate(ctx context.Context) error { return nil } -func stopPauseProcess() error { +func (r *Runtime) stopPauseProcess() error { return nil } diff --git a/libpod/shutdown/handler.go b/libpod/shutdown/handler.go index 87538dec91..f0f228b19e 100644 --- a/libpod/shutdown/handler.go +++ b/libpod/shutdown/handler.go @@ -10,6 +10,10 @@ import ( "github.com/sirupsen/logrus" ) +var ( + ErrHandlerExists error = errors.New("handler with given name already exists") +) + var ( stopped bool sigChan chan os.Signal @@ -98,7 +102,7 @@ func Register(name string, handler func(os.Signal) error) error { } if _, ok := handlers[name]; ok { - return errors.Errorf("handler with name %s already exists", name) + return ErrHandlerExists } handlers[name] = handler diff --git a/libpod/stats.go b/libpod/stats.go index e34739626e..09d990017e 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -34,7 +34,7 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de return stats, define.ErrCtrStateInvalid } - cgroupPath, err := c.CGroupPath() + cgroupPath, err := c.cGroupPath() if err != nil { return nil, err } diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 729639928e..409a74de22 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -37,6 +37,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } + // Override the container name in the body struct + body.Name = query.Name + if len(body.HostConfig.Links) > 0 { utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) return @@ -69,9 +72,6 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } - // Override the container name in the body struct - body.Name = query.Name - ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.ContainerCreate(r.Context(), sg) if err != nil { diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index a4bb72140a..149050209a 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -104,9 +104,6 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { if len(query.Tag) > 0 { output = query.Tag[0] } - if _, found := r.URL.Query()["target"]; found { - output = query.Target - } var additionalNames []string if len(query.Tag) > 1 { @@ -162,7 +159,6 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { reporter := channel.NewWriter(make(chan []byte, 1)) defer reporter.Close() - buildOptions := imagebuildah.BuildOptions{ ContextDirectory: contextDirectory, PullPolicy: pullPolicy, diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 31435ae910..f2cb3147cc 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -152,7 +152,7 @@ func pingNewConnection(ctx context.Context) error { return err } // the ping endpoint sits at / in this case - response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil, nil) + response, err := client.DoRequest(nil, http.MethodGet, "/_ping", nil, nil) if err != nil { return err } @@ -207,11 +207,11 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) ( authMethods = append(authMethods, ssh.Password(pw)) } if len(authMethods) == 0 { - pass, err := terminal.ReadPassword("Login password:") - if err != nil { - return Connection{}, err + callback := func() (string, error) { + pass, err := terminal.ReadPassword("Login password:") + return string(pass), err } - authMethods = append(authMethods, ssh.Password(string(pass))) + authMethods = append(authMethods, ssh.PasswordCallback(callback)) } port := _url.Port() diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index ef0e152649..75ce518b6f 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -714,83 +714,90 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie } for _, signimage := range names { - srcRef, err := alltransports.ParseImageName(signimage) - if err != nil { - return nil, errors.Wrapf(err, "error parsing image name") - } - rawSource, err := srcRef.NewImageSource(ctx, sc) - if err != nil { - return nil, errors.Wrapf(err, "error getting image source") - } - err = rawSource.Close() - if err != nil { - logrus.Errorf("unable to close new image source %q", err) - } - getManifest, _, err := rawSource.GetManifest(ctx, nil) - if err != nil { - return nil, errors.Wrapf(err, "error getting getManifest") - } - dockerReference := rawSource.Reference().DockerReference() - if dockerReference == nil { - return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) - } - var sigStoreDir string - if options.Directory != "" { - sigStoreDir = options.Directory - } - if sigStoreDir == "" { - if rootless.IsRootless() { - sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore") - } else { - var sigStoreURI string - registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) - if registryInfo != nil { - if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" { - sigStoreURI = registryInfo.SigStore - } + err = func() error { + srcRef, err := alltransports.ParseImageName(signimage) + if err != nil { + return errors.Wrapf(err, "error parsing image name") + } + rawSource, err := srcRef.NewImageSource(ctx, sc) + if err != nil { + return errors.Wrapf(err, "error getting image source") + } + defer func() { + if err = rawSource.Close(); err != nil { + logrus.Errorf("unable to close %s image source %q", srcRef.DockerReference().Name(), err) } - if sigStoreURI == "" { - return nil, errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String()) + }() + getManifest, _, err := rawSource.GetManifest(ctx, nil) + if err != nil { + return errors.Wrapf(err, "error getting getManifest") + } + dockerReference := rawSource.Reference().DockerReference() + if dockerReference == nil { + return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) + } + var sigStoreDir string + if options.Directory != "" { + sigStoreDir = options.Directory + } + if sigStoreDir == "" { + if rootless.IsRootless() { + sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore") + } else { + var sigStoreURI string + registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) + if registryInfo != nil { + if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" { + sigStoreURI = registryInfo.SigStore + } + } + if sigStoreURI == "" { + return errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String()) - } - sigStoreDir, err = localPathFromURI(sigStoreURI) - if err != nil { - return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreURI) + } + sigStoreDir, err = localPathFromURI(sigStoreURI) + if err != nil { + return errors.Wrapf(err, "invalid signature storage %s", sigStoreURI) + } } } - } - manifestDigest, err := manifest.Digest(getManifest) - if err != nil { - return nil, err - } - repo := reference.Path(dockerReference) - if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references - return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) - } + manifestDigest, err := manifest.Digest(getManifest) + if err != nil { + return err + } + repo := reference.Path(dockerReference) + if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references + return errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) + } - // create signature - newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy) - if err != nil { - return nil, errors.Wrapf(err, "error creating new signature") - } - // create the signstore file - signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex()) - if err := os.MkdirAll(signatureDir, 0751); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - logrus.Error(err) - continue + // create signature + newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy) + if err != nil { + return errors.Wrapf(err, "error creating new signature") } - } - sigFilename, err := getSigFilename(signatureDir) - if err != nil { - logrus.Errorf("error creating sigstore file: %v", err) - continue - } - err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644) + // create the signstore file + signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex()) + if err := os.MkdirAll(signatureDir, 0751); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + logrus.Error(err) + return nil + } + } + sigFilename, err := getSigFilename(signatureDir) + if err != nil { + logrus.Errorf("error creating sigstore file: %v", err) + return nil + } + err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644) + if err != nil { + logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) + return nil + } + return nil + }() if err != nil { - logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) - continue + return nil, err } } return nil, nil diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 72fd98ac10..ec2532bea9 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/cgroups" "github.com/containers/podman/v2/pkg/domain/entities" @@ -86,7 +87,11 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) return nil } - pausePidPath, err := util.GetRootlessPauseProcessPidPath() + tmpDir, err := ic.Libpod.TmpDir() + if err != nil { + return err + } + pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir) if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } @@ -112,7 +117,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) } became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) - if err := movePauseProcessToScope(); err != nil { + if err := movePauseProcessToScope(ic.Libpod); err != nil { conf, err := ic.Config(context.Background()) if err != nil { return err @@ -133,8 +138,12 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) return nil } -func movePauseProcessToScope() error { - pausePidPath, err := util.GetRootlessPauseProcessPidPath() +func movePauseProcessToScope(r *libpod.Runtime) error { + tmpDir, err := r.TmpDir() + if err != nil { + return err + } + pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir) if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 45a374216e..4f36744cab 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -98,7 +98,6 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener // present. imgName := newImage.InputName if s.Image == newImage.InputName && strings.HasPrefix(newImage.ID(), s.Image) { - imgName = "" names := newImage.Names() if len(names) > 0 { imgName = names[0] @@ -388,7 +387,7 @@ func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Co } if syslog { - command = append(command, "--syslog", "true") + command = append(command, "--syslog") } command = append(command, []string{"container", "cleanup"}...) diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index ddc73ca610..036c7b7a16 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -233,6 +233,8 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value) } toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil)) + case specgen.Private: + fallthrough case specgen.Bridge: portMappings, err := createPortMappings(ctx, s, img) if err != nil { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 8454458a8a..9649873fd1 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -165,7 +165,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt inUserNS = true } } - if inUserNS && s.NetNS.IsHost() { + if inUserNS && s.NetNS.NSMode != specgen.NoNetwork { canMountSys = false } diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index d15745fa07..9d78a02108 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -258,24 +258,22 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) { var cniNetworks []string // Net defaults to Slirp on rootless switch { - case ns == "slirp4netns", strings.HasPrefix(ns, "slirp4netns:"): + case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"): toReturn.NSMode = Slirp - case ns == "pod": + case ns == string(FromPod): toReturn.NSMode = FromPod - case ns == "": + case ns == "" || ns == string(Default) || ns == string(Private): if rootless.IsRootless() { toReturn.NSMode = Slirp } else { toReturn.NSMode = Bridge } - case ns == "bridge": + case ns == string(Bridge): toReturn.NSMode = Bridge - case ns == "none": + case ns == string(NoNetwork): toReturn.NSMode = NoNetwork - case ns == "host": + case ns == string(Host): toReturn.NSMode = Host - case ns == "private": - toReturn.NSMode = Private case strings.HasPrefix(ns, "ns:"): split := strings.SplitN(ns, ":", 2) if len(split) != 2 { @@ -283,7 +281,7 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) { } toReturn.NSMode = Path toReturn.Value = split[1] - case strings.HasPrefix(ns, "container:"): + case strings.HasPrefix(ns, string(FromContainer)+":"): split := strings.SplitN(ns, ":", 2) if len(split) != 2 { return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:") diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go index 2d636a7cb0..a63c764152 100644 --- a/pkg/util/utils_supported.go +++ b/pkg/util/utils_supported.go @@ -99,7 +99,8 @@ func GetRootlessConfigHomeDir() (string, error) { } // GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for -// the pause process +// the pause process. +// DEPRECATED - switch to GetRootlessPauseProcessPidPathGivenDir func GetRootlessPauseProcessPidPath() (string, error) { runtimeDir, err := GetRuntimeDir() if err != nil { @@ -107,3 +108,13 @@ func GetRootlessPauseProcessPidPath() (string, error) { } return filepath.Join(runtimeDir, "libpod", "pause.pid"), nil } + +// GetRootlessPauseProcessPidPathGivenDir returns the path to the file that +// holds the PID of the pause process, given the location of Libpod's temporary +// files. +func GetRootlessPauseProcessPidPathGivenDir(libpodTmpDir string) (string, error) { + if libpodTmpDir == "" { + return "", errors.Errorf("must provide non-empty tmporary directory") + } + return filepath.Join(libpodTmpDir, "pause.pid"), nil +} diff --git a/pkg/util/utils_windows.go b/pkg/util/utils_windows.go index 9bba2d1ee7..46ca5e7f18 100644 --- a/pkg/util/utils_windows.go +++ b/pkg/util/utils_windows.go @@ -25,6 +25,12 @@ func GetRootlessPauseProcessPidPath() (string, error) { return "", errors.Wrap(errNotImplemented, "GetRootlessPauseProcessPidPath") } +// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for +// the pause process +func GetRootlessPauseProcessPidPathGivenDir(unused string) (string, error) { + return "", errors.Wrap(errNotImplemented, "GetRootlessPauseProcessPidPath") +} + // GetRuntimeDir returns the runtime directory func GetRuntimeDir() (string, error) { return "", errors.New("this function is not implemented for windows") diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index b35c272159..5c35edf2bb 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -169,7 +169,7 @@ t GET containers/$cid/json 200 \ .Args[1]="param2" t DELETE containers/$cid 204 -# test only set the entrpoint, Cmd should be [] +# test only set the entrypoint, Cmd should be [] t POST containers/create '"Image":"'$IMAGE'","Entrypoint":["echo","param1"]' 201 \ .Id~[0-9a-f]\\{64\\} cid=$(jq -r '.Id' <<<"$output") 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 52348d4f46..abb8200532 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -172,7 +172,7 @@ def test_post_create_compat_connect(self): self.assertEqual(net_default.status_code, 201, net_default.text) create = requests.post( - PODMAN_URL + "/v1.40/containers/create?name=postCreate", + PODMAN_URL + "/v1.40/containers/create?name=postCreateConnect", json={ "Cmd": ["top"], "Image": "alpine:latest", diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 906153c0fa..866162f7f3 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -203,35 +203,35 @@ var _ = Describe("Podman run", func() { session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("search foobar.com") + session.LineInOutputStartsWith("search foobar.com") }) It("podman run add dns server", func() { session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("server 1.2.3.4") + session.LineInOutputStartsWith("server 1.2.3.4") }) It("podman run add dns option", func() { session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("options debug") + session.LineInOutputStartsWith("options debug") }) It("podman run containers.conf remove all search domain", func() { session := podmanTest.Podman([]string{"run", "--dns-search=.", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("search")).To(BeFalse()) + Expect(session.LineInOutputStartsWith("search")).To(BeFalse()) }) It("podman run use containers.conf search domain", func() { session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("search")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("search")).To(BeTrue()) Expect(session.OutputToString()).To(ContainSubstring("foobar.com")) Expect(session.OutputToString()).To(ContainSubstring("1.2.3.4")) @@ -275,7 +275,7 @@ var _ = Describe("Podman run", func() { session = podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("search")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("search")).To(BeTrue()) Expect(session.OutputToString()).To(ContainSubstring("foobar.com")) Expect(session.OutputToString()).To(ContainSubstring("1.2.3.4")) Expect(session.OutputToString()).To(ContainSubstring("debug")) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 4c65a85d59..281b2c313c 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -41,8 +41,8 @@ var _ = Describe("Podman images", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) - Expect(session.LineInOuputStartsWith("quay.io/libpod/alpine")).To(BeTrue()) - Expect(session.LineInOuputStartsWith("quay.io/libpod/busybox")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("quay.io/libpod/busybox")).To(BeTrue()) }) It("podman image List", func() { @@ -50,8 +50,8 @@ var _ = Describe("Podman images", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) - Expect(session.LineInOuputStartsWith("quay.io/libpod/alpine")).To(BeTrue()) - Expect(session.LineInOuputStartsWith("quay.io/libpod/busybox")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("quay.io/libpod/busybox")).To(BeTrue()) }) It("podman images with multiple tags", func() { @@ -86,8 +86,8 @@ var _ = Describe("Podman images", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) - Expect(session.LineInOuputStartsWith("quay.io/libpod/alpine")).To(BeTrue()) - Expect(session.LineInOuputStartsWith("quay.io/libpod/busybox")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("quay.io/libpod/busybox")).To(BeTrue()) }) It("podman empty images list in JSON format", func() { @@ -278,7 +278,7 @@ WORKDIR /test It("podman images sort by values", func() { sortValueTest := func(value string, result int, format string) []string { f := fmt.Sprintf("{{.%s}}", format) - session := podmanTest.Podman([]string{"images", "--sort", value, "--format", f}) + session := podmanTest.Podman([]string{"images", "--noheading", "--sort", value, "--format", f}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(result)) diff --git a/test/e2e/import_test.go b/test/e2e/import_test.go index 1be4ef9208..bd465bf388 100644 --- a/test/e2e/import_test.go +++ b/test/e2e/import_test.go @@ -87,7 +87,7 @@ var _ = Describe("Podman import", func() { results := podmanTest.Podman([]string{"history", "imported-image", "--format", "{{.Comment}}"}) results.WaitWithDefaultTimeout() Expect(results.ExitCode()).To(Equal(0)) - Expect(results.LineInOuputStartsWith("importing container test message")).To(BeTrue()) + Expect(results.LineInOutputStartsWith("importing container test message")).To(BeTrue()) }) It("podman import with change flag CMD=", func() { diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index a3ce8bd694..4aaf2cbc17 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -61,7 +61,7 @@ var _ = Describe("Podman port", func() { result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) port := strings.Split(result.OutputToStringArray()[0], ":")[1] - Expect(result.LineInOuputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue()) + Expect(result.LineInOutputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue()) }) It("podman container port -l nginx", func() { @@ -79,7 +79,7 @@ var _ = Describe("Podman port", func() { result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) port := strings.Split(result.OutputToStringArray()[0], ":")[1] - Expect(result.LineInOuputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue()) + Expect(result.LineInOutputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue()) }) It("podman port -l port nginx", func() { @@ -97,7 +97,7 @@ var _ = Describe("Podman port", func() { result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) port := strings.Split(result.OutputToStringArray()[0], ":")[1] - Expect(result.LineInOuputStartsWith(fmt.Sprintf("0.0.0.0:%s", port))).To(BeTrue()) + Expect(result.LineInOutputStartsWith(fmt.Sprintf("0.0.0.0:%s", port))).To(BeTrue()) }) It("podman port -a nginx", func() { @@ -124,7 +124,7 @@ var _ = Describe("Podman port", func() { result := podmanTest.Podman([]string{"port", "portcheck"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - result.LineInOuputStartsWith("80/tcp -> 0.0.0.0:") + result.LineInOutputStartsWith("80/tcp -> 0.0.0.0:") }) It("podman port multiple ports", func() { @@ -142,12 +142,12 @@ var _ = Describe("Podman port", func() { result1 := podmanTest.Podman([]string{"port", "test", "5000"}) result1.WaitWithDefaultTimeout() Expect(result1.ExitCode()).To(BeZero()) - Expect(result1.LineInOuputStartsWith("0.0.0.0:5000")).To(BeTrue()) + Expect(result1.LineInOutputStartsWith("0.0.0.0:5000")).To(BeTrue()) // Check that the second port was honored result2 := podmanTest.Podman([]string{"port", "test", "5001"}) result2.WaitWithDefaultTimeout() Expect(result2.ExitCode()).To(BeZero()) - Expect(result2.LineInOuputStartsWith("0.0.0.0:5001")).To(BeTrue()) + Expect(result2.LineInOutputStartsWith("0.0.0.0:5001")).To(BeTrue()) }) }) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index f1b055d6d1..1f13190ba1 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -384,7 +384,7 @@ var _ = Describe("Podman pull", func() { session := podmanTest.Podman([]string{"pull", "--all-tags", "k8s.gcr.io/pause"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("Pulled Images:")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("Pulled Images:")).To(BeTrue()) session = podmanTest.Podman([]string{"images"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index ff018f5d8f..c8996c5e83 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -36,14 +36,14 @@ var _ = Describe("Podman run dns", func() { session := podmanTest.Podman([]string{"run", "--dns-search=foobar.com", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("search foobar.com") + session.LineInOutputStartsWith("search foobar.com") }) It("podman run remove all search domain", func() { session := podmanTest.Podman([]string{"run", "--dns-search=.", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("search")).To(BeFalse()) + Expect(session.LineInOutputStartsWith("search")).To(BeFalse()) }) It("podman run add bad dns server", func() { @@ -56,14 +56,14 @@ var _ = Describe("Podman run dns", func() { session := podmanTest.Podman([]string{"run", "--dns=1.2.3.4", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("server 1.2.3.4") + session.LineInOutputStartsWith("server 1.2.3.4") }) It("podman run add dns option", func() { session := podmanTest.Podman([]string{"run", "--dns-opt=debug", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("options debug") + session.LineInOutputStartsWith("options debug") }) It("podman run add bad host", func() { @@ -76,8 +76,8 @@ var _ = Describe("Podman run dns", func() { session := podmanTest.Podman([]string{"run", "--add-host=foobar:1.1.1.1", "--add-host=foobaz:2001:db8::68", ALPINE, "cat", "/etc/hosts"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session.LineInOuputStartsWith("1.1.1.1 foobar") - session.LineInOuputStartsWith("2001:db8::68 foobaz") + session.LineInOutputStartsWith("1.1.1.1 foobar") + session.LineInOutputStartsWith("2001:db8::68 foobaz") }) It("podman run add hostname", func() { diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index 2185d6b13d..cac3d759d0 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -99,12 +99,12 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"] session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("Linux")).To(BeTrue()) session = podmanTest.Podman([]string{"run", "--entrypoint", "", "foobar.com/entrypoint:latest", "uname"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("Linux")).To(BeTrue()) }) It("podman run user entrypoint with command overrides image entrypoint and image cmd", func() { @@ -116,6 +116,6 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"] session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest", "-r"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOuputStartsWith("Linux")).To(BeFalse()) + Expect(session.LineInOutputStartsWith("Linux")).To(BeFalse()) }) }) diff --git a/test/e2e/run_memory_test.go b/test/e2e/run_memory_test.go index b3913c1e62..ad3a2b54fd 100644 --- a/test/e2e/run_memory_test.go +++ b/test/e2e/run_memory_test.go @@ -38,7 +38,7 @@ var _ = Describe("Podman run memory", func() { var session *PodmanSessionIntegration if CGROUPSV2 { - session = podmanTest.Podman([]string{"run", "--memory=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.max"}) + session = podmanTest.Podman([]string{"run", "--memory=40m", "--net=none", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.max"}) } else { session = podmanTest.Podman([]string{"run", "--memory=40m", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"}) } @@ -55,7 +55,7 @@ var _ = Describe("Podman run memory", func() { var session *PodmanSessionIntegration if CGROUPSV2 { - session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"}) + session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", "--net=none", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"}) } else { session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.soft_limit_in_bytes"}) } @@ -81,7 +81,7 @@ var _ = Describe("Podman run memory", func() { var session *PodmanSessionIntegration if CGROUPSV2 { - session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"}) + session = podmanTest.Podman([]string{"run", "--net=none", "--memory-reservation=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"}) } else { session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.soft_limit_in_bytes"}) } diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 3e80e953ee..3fb00a28be 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -49,9 +49,28 @@ var _ = Describe("Podman run networking", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run network connection with default", func() { + session := podmanTest.Podman([]string{"run", "--network", "default", ALPINE, "wget", "www.podman.io"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman run network connection with none", func() { + session := podmanTest.Podman([]string{"run", "--network", "none", ALPINE, "wget", "www.podman.io"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + Expect(session.ErrorToString()).To(ContainSubstring("wget: bad address 'www.podman.io'")) + }) + + It("podman run network connection with private", func() { + session := podmanTest.Podman([]string{"run", "--network", "private", ALPINE, "wget", "www.podman.io"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run network connection with loopback", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.podman.io"}) - session.Wait(90) + session := podmanTest.Podman([]string{"run", "--network", "host", ALPINE, "wget", "www.podman.io"}) + session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 0d65a3e596..5831bb2f9f 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1267,7 +1267,7 @@ USER mail` It("podman run verify pids-limit", func() { SkipIfCgroupV1("pids-limit not supported on cgroup V1") limit := "4321" - session := podmanTest.Podman([]string{"run", "--pids-limit", limit, "--rm", ALPINE, "cat", "/sys/fs/cgroup/pids.max"}) + session := podmanTest.Podman([]string{"run", "--pids-limit", limit, "--net=none", "--rm", ALPINE, "cat", "/sys/fs/cgroup/pids.max"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring(limit)) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 98bb0cc576..7379e5571e 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -197,9 +197,16 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z local format=$2 run_podman images --sort repository --format "$format" - _check_line 0 ${aaa_name} ${aaa_tag} - _check_line 1 "${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/${PODMAN_TEST_IMAGE_NAME}" "${PODMAN_TEST_IMAGE_TAG}" - _check_line 2 ${zzz_name} ${zzz_tag} + + line_no=0 + if [[ $format == table* ]]; then + # skip headers from table command + line_no=1 + fi + + _check_line $line_no ${aaa_name} ${aaa_tag} + _check_line $((line_no+1)) "${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/${PODMAN_TEST_IMAGE_NAME}" "${PODMAN_TEST_IMAGE_TAG}" + _check_line $((line_no+2)) ${zzz_name} ${zzz_tag} } # Begin the test: tag $IMAGE with both the given names diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 71831da104..bf7f67f7f1 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -532,6 +532,17 @@ json-file | f run_podman untag $IMAGE $newtag $newtag2 } +# Regression test for issue #8558 +@test "podman run on untagged image: make sure that image metadata is set" { + run_podman inspect $IMAGE --format "{{.ID}}" + imageID="$output" + + run_podman untag $IMAGE + run_podman run --rm $imageID ls + + run_podman tag $imageID $IMAGE +} + @test "podman run with --net=host and --port prints warning" { run_podman run -d --rm -p 8080 --net=host $IMAGE ls > /dev/null is "$output" ".*Port mappings have been discarded as one of the Host, Container, Pod, and None network modes are in use" diff --git a/test/utils/podmansession_test.go b/test/utils/podmansession_test.go index d0e2f3d06f..763cb4f26a 100644 --- a/test/utils/podmansession_test.go +++ b/test/utils/podmansession_test.go @@ -53,8 +53,8 @@ var _ = Describe("PodmanSession test", func() { }) It("Test LineInOutputStartsWith", func() { - Expect(session.LineInOuputStartsWith("Podman")).To(BeTrue()) - Expect(session.LineInOuputStartsWith("Session")).To(Not(BeTrue())) + Expect(session.LineInOutputStartsWith("Podman")).To(BeTrue()) + Expect(session.LineInOutputStartsWith("Session")).To(Not(BeTrue())) }) It("Test LineInOutputContains", func() { diff --git a/test/utils/utils.go b/test/utils/utils.go index dd836f2587..d089396787 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -277,7 +277,7 @@ func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { // LineInOutputStartsWith returns true if a line in a // session output starts with the supplied string -func (s *PodmanSession) LineInOuputStartsWith(term string) bool { +func (s *PodmanSession) LineInOutputStartsWith(term string) bool { for _, i := range s.OutputToStringArray() { if strings.HasPrefix(i, term) { return true diff --git a/utils/utils_supported.go b/utils/utils_supported.go index bcaa2c61ac..6f517dc72e 100644 --- a/utils/utils_supported.go +++ b/utils/utils_supported.go @@ -43,6 +43,15 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error { ch := make(chan string) _, err = conn.StartTransientUnit(unitName, "replace", properties, ch) if err != nil { + // On errors check if the cgroup already exists, if it does move the process there + if props, err := conn.GetUnitTypeProperties(unitName, "Scope"); err == nil { + if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" { + if err := moveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err != nil { + return err + } + return nil + } + } return err } defer conn.Close() @@ -101,6 +110,13 @@ func GetCgroupProcess(pid int) (string, error) { // MoveUnderCgroupSubtree moves the PID under a cgroup subtree. func MoveUnderCgroupSubtree(subtree string) error { + return moveUnderCgroup("", subtree, nil) +} + +// moveUnderCgroup moves a group of processes to a new cgroup. +// If cgroup is the empty string, then the current calling process cgroup is used. +// If processes is empty, then the processes from the current cgroup are moved. +func moveUnderCgroup(cgroup, subtree string, processes []uint32) error { procFile := "/proc/self/cgroup" f, err := os.Open(procFile) if err != nil { @@ -140,13 +156,12 @@ func MoveUnderCgroupSubtree(subtree string) error { cgroupRoot = filepath.Join(cgroupRoot, controller) } - processes, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs")) - if err != nil { - return err + parentCgroup := cgroup + if parentCgroup == "" { + parentCgroup = parts[2] } - - newCgroup := filepath.Join(cgroupRoot, parts[2], subtree) - if err := os.Mkdir(newCgroup, 0755); err != nil { + newCgroup := filepath.Join(cgroupRoot, parentCgroup, subtree) + if err := os.Mkdir(newCgroup, 0755); err != nil && !os.IsExist(err) { return err } @@ -156,9 +171,21 @@ func MoveUnderCgroupSubtree(subtree string) error { } defer f.Close() - for _, pid := range bytes.Split(processes, []byte("\n")) { - if _, err := f.Write(pid); err != nil { - logrus.Warnf("Cannot move process %s to cgroup %q", pid, newCgroup) + if len(processes) > 0 { + for _, pid := range processes { + if _, err := f.Write([]byte(fmt.Sprintf("%d\n", pid))); err != nil { + logrus.Warnf("Cannot move process %d to cgroup %q", pid, newCgroup) + } + } + } else { + processesData, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs")) + if err != nil { + return err + } + for _, pid := range bytes.Split(processesData, []byte("\n")) { + if _, err := f.Write(pid); err != nil { + logrus.Warnf("Cannot move process %s to cgroup %q", string(pid), newCgroup) + } } } } diff --git a/vendor/github.com/containers/image/v5/copy/copy.go b/vendor/github.com/containers/image/v5/copy/copy.go index 4d5b076891..485db4d30a 100644 --- a/vendor/github.com/containers/image/v5/copy/copy.go +++ b/vendor/github.com/containers/image/v5/copy/copy.go @@ -53,6 +53,14 @@ var ( // compressionBufferSize is the buffer size used to compress a blob var compressionBufferSize = 1048576 +// expectedCompressionFormats is used to check if a blob with a specified media type is compressed +// using the algorithm that the media type says it should be compressed with +var expectedCompressionFormats = map[string]*compression.Algorithm{ + imgspecv1.MediaTypeImageLayerGzip: &compression.Gzip, + imgspecv1.MediaTypeImageLayerZstd: &compression.Zstd, + manifest.DockerV2Schema2LayerMediaType: &compression.Gzip, +} + // newDigestingReader returns an io.Reader implementation with contents of source, which will eventually return a non-EOF error // or set validationSucceeded/validationFailed to true if the source stream does/does not match expectedDigest. // (neither is set if EOF is never reached). @@ -1234,6 +1242,10 @@ func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, sr isCompressed := decompressor != nil destStream = bar.ProxyReader(destStream) + if expectedCompressionFormat, known := expectedCompressionFormats[srcInfo.MediaType]; known && isCompressed && compressionFormat.Name() != expectedCompressionFormat.Name() { + logrus.Debugf("blob %s with type %s should be compressed with %s, but compressor appears to be %s", srcInfo.Digest.String(), srcInfo.MediaType, expectedCompressionFormat.Name(), compressionFormat.Name()) + } + // === Send a copy of the original, uncompressed, stream, to a separate path if necessary. var originalLayerReader io.Reader // DO NOT USE this other than to drain the input if no other consumer in the pipeline has done so. if getOriginalLayerCopyWriter != nil { diff --git a/vendor/github.com/containers/image/v5/pkg/compression/compression.go b/vendor/github.com/containers/image/v5/pkg/compression/compression.go index 04d231c6d6..d5cfd8d31c 100644 --- a/vendor/github.com/containers/image/v5/pkg/compression/compression.go +++ b/vendor/github.com/containers/image/v5/pkg/compression/compression.go @@ -91,7 +91,8 @@ func CompressStream(dest io.Writer, algo Algorithm, level *int) (io.WriteCloser, return internal.AlgorithmCompressor(algo)(dest, level) } -// DetectCompressionFormat returns a DecompressorFunc if the input is recognized as a compressed format, nil otherwise. +// DetectCompressionFormat returns an Algorithm and DecompressorFunc if the input is recognized as a compressed format, an invalid +// value and nil otherwise. // Because it consumes the start of input, other consumers must use the returned io.Reader instead to also read from the beginning. func DetectCompressionFormat(input io.Reader) (Algorithm, DecompressorFunc, io.Reader, error) { buffer := [8]byte{} diff --git a/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go index e02703d773..198ac1cc64 100644 --- a/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go +++ b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go @@ -225,9 +225,8 @@ func (c *PullCandidate) Record() error { // 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. +// normalized with the "latest" tag. An error is returned 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 @@ -312,6 +311,10 @@ func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { if err != nil { return nil, err } + // Error out if there's no matching alias and no search registries. + if len(unqualifiedSearchRegistries) == 0 { + return nil, errors.Errorf("short-name %q did not resolve to an alias and no unqualified-search registries are defined in %q", name, usrConfig) + } resolved.originDescription = usrConfig for _, reg := range unqualifiedSearchRegistries { @@ -331,10 +334,8 @@ func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { 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 { + // If we have only one candidate, there's no ambiguity. + if len(resolved.PullCandidates) == 1 { return resolved, nil } diff --git a/vendor/github.com/containers/image/v5/signature/policy_config.go b/vendor/github.com/containers/image/v5/signature/policy_config.go index a4873e9fab..d8cc4a09bf 100644 --- a/vendor/github.com/containers/image/v5/signature/policy_config.go +++ b/vendor/github.com/containers/image/v5/signature/policy_config.go @@ -19,6 +19,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/transports" @@ -507,6 +508,8 @@ func newPolicyReferenceMatchFromJSON(data []byte) (PolicyReferenceMatch, error) res = &prmExactReference{} case prmTypeExactRepository: res = &prmExactRepository{} + case prmTypeRemapIdentity: + res = &prmRemapIdentity{} default: return nil, InvalidPolicyFormatError(fmt.Sprintf("Unknown policy reference match type \"%s\"", typeField.Type)) } @@ -693,3 +696,76 @@ func (prm *prmExactRepository) UnmarshalJSON(data []byte) error { *prm = *res return nil } + +// Private objects for validateIdentityRemappingPrefix +var ( + // remapIdentityDomainRegexp matches exactly a reference domain (name[:port]) + remapIdentityDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.String() + "$") + // remapIdentityDomainPrefixRegexp matches a reference that starts with a domain; + // we need this because reference.NameRegexp accepts short names with docker.io implied. + remapIdentityDomainPrefixRegexp = regexp.MustCompile("^" + reference.DomainRegexp.String() + "/") + // remapIdentityNameRegexp matches exactly a reference.Named name (possibly unnormalized) + remapIdentityNameRegexp = regexp.MustCompile("^" + reference.NameRegexp.String() + "$") +) + +// validateIdentityRemappingPrefix returns an InvalidPolicyFormatError if s is detected to be invalid +// for the Prefix or SignedPrefix values of prmRemapIdentity. +// Note that it may not recognize _all_ invalid values. +func validateIdentityRemappingPrefix(s string) error { + if remapIdentityDomainRegexp.MatchString(s) || + (remapIdentityNameRegexp.MatchString(s) && remapIdentityDomainPrefixRegexp.MatchString(s)) { + // FIXME? This does not reject "shortname" nor "ns/shortname", because docker/reference + // does not provide an API for the short vs. long name logic. + // It will either not match, or fail in the ParseNamed call of + // prmRemapIdentity.remapReferencePrefix when trying to use such a prefix. + return nil + } + return InvalidPolicyFormatError(fmt.Sprintf("prefix %q is not valid", s)) +} + +// newPRMRemapIdentity is NewPRMRemapIdentity, except it returns the private type. +func newPRMRemapIdentity(prefix, signedPrefix string) (*prmRemapIdentity, error) { + if err := validateIdentityRemappingPrefix(prefix); err != nil { + return nil, err + } + if err := validateIdentityRemappingPrefix(signedPrefix); err != nil { + return nil, err + } + return &prmRemapIdentity{ + prmCommon: prmCommon{Type: prmTypeRemapIdentity}, + Prefix: prefix, + SignedPrefix: signedPrefix, + }, nil +} + +// NewPRMRemapIdentity returns a new "remapIdentity" PolicyRepositoryMatch. +func NewPRMRemapIdentity(prefix, signedPrefix string) (PolicyReferenceMatch, error) { + return newPRMRemapIdentity(prefix, signedPrefix) +} + +// Compile-time check that prmRemapIdentity implements json.Unmarshaler. +var _ json.Unmarshaler = (*prmRemapIdentity)(nil) + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (prm *prmRemapIdentity) UnmarshalJSON(data []byte) error { + *prm = prmRemapIdentity{} + var tmp prmRemapIdentity + if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{ + "type": &tmp.Type, + "prefix": &tmp.Prefix, + "signedPrefix": &tmp.SignedPrefix, + }); err != nil { + return err + } + + if tmp.Type != prmTypeRemapIdentity { + return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type)) + } + + res, err := newPRMRemapIdentity(tmp.Prefix, tmp.SignedPrefix) + if err != nil { + return err + } + *prm = *res + return nil +} diff --git a/vendor/github.com/containers/image/v5/signature/policy_reference_match.go b/vendor/github.com/containers/image/v5/signature/policy_reference_match.go index e2a21f01d8..064866cf6c 100644 --- a/vendor/github.com/containers/image/v5/signature/policy_reference_match.go +++ b/vendor/github.com/containers/image/v5/signature/policy_reference_match.go @@ -4,6 +4,7 @@ package signature import ( "fmt" + "strings" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/transports" @@ -36,12 +37,9 @@ func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, sign return signature.String() == intended.String() } -func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { - intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference) - if err != nil { - return false - } - +// matchRepoDigestOrExactReferenceValues implements prmMatchRepoDigestOrExact.matchesDockerReference +// using reference.Named values. +func matchRepoDigestOrExactReferenceValues(intended, signature reference.Named) bool { // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. if reference.IsNameOnly(signature) { return false @@ -58,6 +56,13 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse return false } } +func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { + intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference) + if err != nil { + return false + } + return matchRepoDigestOrExactReferenceValues(intended, signature) +} func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference) @@ -99,3 +104,51 @@ func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, } return signature.Name() == intended.Name() } + +// refMatchesPrefix returns true if ref matches prm.Prefix. +func (prm *prmRemapIdentity) refMatchesPrefix(ref reference.Named) bool { + name := ref.Name() + switch { + case len(name) < len(prm.Prefix): + return false + case len(name) == len(prm.Prefix): + return name == prm.Prefix + case len(name) > len(prm.Prefix): + // We are matching only ref.Name(), not ref.String(), so the only separator we are + // expecting is '/': + // - '@' is only valid to separate a digest, i.e. not a part of ref.Name() + // - similarly ':' to mark a tag would not be a part of ref.Name(); it can be a part of a + // host:port domain syntax, but we don't treat that specially and require an exact match + // of the domain. + return strings.HasPrefix(name, prm.Prefix) && name[len(prm.Prefix)] == '/' + default: + panic("Internal error: impossible comparison outcome") + } +} + +// remapReferencePrefix returns the result of remapping ref, if it matches prm.Prefix +// or the original ref if it does not. +func (prm *prmRemapIdentity) remapReferencePrefix(ref reference.Named) (reference.Named, error) { + if !prm.refMatchesPrefix(ref) { + return ref, nil + } + refString := ref.String() + newNamedRef := strings.Replace(refString, prm.Prefix, prm.SignedPrefix, 1) + newParsedRef, err := reference.ParseNamed(newNamedRef) + if err != nil { + return nil, fmt.Errorf(`error rewriting reference from "%s" to "%s": %v`, refString, newNamedRef, err) + } + return newParsedRef, nil +} + +func (prm *prmRemapIdentity) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { + intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference) + if err != nil { + return false + } + intended, err = prm.remapReferencePrefix(intended) + if err != nil { + return false + } + return matchRepoDigestOrExactReferenceValues(intended, signature) +} diff --git a/vendor/github.com/containers/image/v5/signature/policy_types.go b/vendor/github.com/containers/image/v5/signature/policy_types.go index d3b33bb7ac..c6819929bd 100644 --- a/vendor/github.com/containers/image/v5/signature/policy_types.go +++ b/vendor/github.com/containers/image/v5/signature/policy_types.go @@ -121,6 +121,7 @@ const ( prmTypeMatchRepository prmTypeIdentifier = "matchRepository" prmTypeExactReference prmTypeIdentifier = "exactReference" prmTypeExactRepository prmTypeIdentifier = "exactRepository" + prmTypeRemapIdentity prmTypeIdentifier = "remapIdentity" ) // prmMatchExact is a PolicyReferenceMatch with type = prmMatchExact: the two references must match exactly. @@ -150,3 +151,13 @@ type prmExactRepository struct { prmCommon DockerRepository string `json:"dockerRepository"` } + +// prmRemapIdentity is a PolicyReferenceMatch with type = prmRemapIdentity: like prmMatchRepoDigestOrExact, +// except that a namespace (at least a host:port, at most a single repository) is substituted before matching the two references. +type prmRemapIdentity struct { + prmCommon + Prefix string `json:"prefix"` + SignedPrefix string `json:"signedPrefix"` + // Possibly let the users make a choice for tag/digest matching behavior + // similar to prmMatchExact/prmMatchRepository? +} diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index 14e553c9f1..48ecf938c1 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -6,9 +6,9 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 5 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 8 + VersionMinor = 9 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 1 + VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "" diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go index 2ceebc5522..846636d75b 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.go +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -5,95 +5,70 @@ import ( "fmt" "io" "os" + "sort" + "strings" + + "github.com/spf13/pflag" ) // Annotations for Bash completion. const ( - BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" - // BashCompCustom should be avoided as it only works for bash. - // Function RegisterFlagCompletionFunc() should be used instead. + BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" BashCompCustom = "cobra_annotation_bash_completion_custom" BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" ) -// GenBashCompletion generates bash completion file and writes to the passed writer. -func (c *Command) GenBashCompletion(w io.Writer) error { - return c.genBashCompletion(w, false) -} - -// 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) -} - -// 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 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 } -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) +# 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 } -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 +__%[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 } -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() +__%[1]s_contains_word() { - if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then - echo "$*" >> "${BASH_COMP_DEBUG_FILE}" - fi + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 } -__%[1]s_perform_completion() +__%[1]s_handle_go_custom_completion() { - __%[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[*]}," + __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#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 flagPrefix + local out requestComp lastParam lastChar comp directive args # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases @@ -102,24 +77,16 @@ __%[1]s_perform_completion() lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} - __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}" + __%[1]s_debug "${FUNCNAME[0]}: 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 "Adding extra empty parameter" + __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" requestComp="${requestComp} \"\"" fi - # When completing a flag with an = (e.g., %[1]s -n=) - # 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}" + __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" # Use eval to handle any environment variables and such out=$(eval "${requestComp}" 2>/dev/null) @@ -131,23 +98,23 @@ __%[1]s_perform_completion() # There is not directive specified directive=0 fi - __%[1]s_debug "The completion directive is: ${directive}" - __%[1]s_debug "The completions are: ${out[*]}" + __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. - __%[1]s_debug "Received error from custom completion go code" + __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" return else if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then - __%[1]s_debug "Activating no space" + __%[1]s_debug "${FUNCNAME[0]}: activating no space" compopt -o nospace fi fi if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then - __%[1]s_debug "Activating no file completion" + __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" compopt +o default fi fi @@ -156,7 +123,6 @@ __%[1]s_perform_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 @@ -168,173 +134,545 @@ __%[1]s_perform_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" - pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return + __%[1]s_handle_subdirs_in_dir_flag "$subdir" 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 - comp=${comp%%%%$tab*} - if ((${#comp}>longest)); then - longest=${#comp} + 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 fi - done < <(printf "%%s\n" "${out[@]}") - local completions=() - while IFS='' read -r comp; do - if [ -z "$comp" ]; then - continue + # 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 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 - __%[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[@]}") + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + 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 - __%[1]s_handle_special_char "$cur" : - __%[1]s_handle_special_char "$cur" = + c=$((c+1)) + } -__%[1]s_handle_special_char() +__%[1]s_handle_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 + __%[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=() fi + + nouns+=("${words[c]}") + c=$((c+1)) } -__%[1]s_format_comp_descriptions() +__%[1]s_handle_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 + __%[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" else - # Don't pad the descriptions so we can fit more text after the completion - maxdesclength=$(( COLUMNS - ${#comp} - 4 )) + next_command="_${words[c]//:/__}" fi + fi + c=$((c+1)) + __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} - # 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)" +__%[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 fi + else + __%[1]s_handle_noun fi + __%[1]s_handle_word +} - # Must use printf to escape all special characters - printf "%%q" "${comp}" +`, name, ShellCompNoDescRequestCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } -__start_%[1]s() -{ +func writePostscript(buf *bytes.Buffer, name string) { + name = strings.Replace(name, ":", "__", -1) + buf.WriteString(fmt.Sprintf("__start_%s()\n", name)) + buf.WriteString(fmt.Sprintf(`{ 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 - COMPREPLY=() - _get_comp_words_by_ref -n "=:" cur prev words cword - - __%[1]s_perform_completion + 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 } -if [[ $(type -t compopt) = "builtin" ]]; then - complete -o default -F __start_%[1]s %[1]s +`, name)) + buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_%s %s else - complete -o default -o nospace -F __start_%[1]s %[1]s + complete -o default -o nospace -F __start_%s %s fi -# ex: ts=4 sw=4 et filetype=sh -`, name, compCmd, - ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) +`, 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 ___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) } diff --git a/vendor/github.com/spf13/cobra/custom_completions.go b/vendor/github.com/spf13/cobra/custom_completions.go index e2c68ae1e7..f9e88e081f 100644 --- a/vendor/github.com/spf13/cobra/custom_completions.go +++ b/vendor/github.com/spf13/cobra/custom_completions.go @@ -51,11 +51,6 @@ 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. @@ -99,12 +94,6 @@ 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") } @@ -160,6 +149,10 @@ 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 : @@ -369,10 +362,6 @@ 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 @@ -453,16 +442,7 @@ 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 = - 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] - } + flagName = strings.TrimLeft(lastArg[:index], "-") lastArg = lastArg[index+1:] flagWithEqual = true } else { @@ -479,16 +459,8 @@ 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 { - 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:] - } + flagName = strings.TrimLeft(prevArg, "-") + // Remove the uncompleted flag or else there could be an error created // for an invalid value for that flag trimmedArgs = args[:len(args)-1] @@ -541,65 +513,6 @@ 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 "___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 aeaef0087d..eaae9bca86 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" + __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv" - set args (string split -- " " (commandline -c)) + set args (string split -- " " "$argv") set lastArg "$args[-1]" __%[1]s_debug "args: $args" @@ -71,22 +71,31 @@ function __%[1]s_perform_completion printf "%%s\n" "$directiveLine" end -# 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 +# 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 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 - set results (__%[1]s_perform_completion) + # 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 __%[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 @@ -101,8 +110,6 @@ 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 @@ -112,14 +119,6 @@ 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 @@ -129,6 +128,7 @@ 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,51 +137,27 @@ function __%[1]s_prepare_completions __%[1]s_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) - __%[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 + # 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]. end - return 0 + 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) end # Since Fish completions are only loaded once the user triggers them, we trigger them ourselves @@ -194,14 +170,21 @@ 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 call to __%[1]s_prepare_completions will setup __%[1]s_comp_results -# which provides the program's completion choices. +# 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. complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' `, nameForVar, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } // 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 ef290e2eb1..92a70394a9 100644 --- a/vendor/github.com/spf13/cobra/zsh_completions.go +++ b/vendor/github.com/spf13/cobra/zsh_completions.go @@ -94,10 +94,8 @@ _%[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 comp lastComp noSpace + local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp local -a completions __%[1]s_debug "\n========= starting completion logic ==========" @@ -165,6 +163,7 @@ _%[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. @@ -176,17 +175,13 @@ _%[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 @@ -213,40 +208,25 @@ _%[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 - 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 + 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" else - __%[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 + # Perform file completion + __%[1]s_debug "activating file completion" + _arguments '*:filename:_files'" ${flagPrefix}" fi + else + _describe "completions" completions $(echo $flagPrefix) fi } @@ -256,6 +236,5 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then fi `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 674b7a4e4a..b4222596a0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -104,7 +104,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.8.1 +# github.com/containers/image/v5 v5.9.0 github.com/containers/image/v5/copy github.com/containers/image/v5/directory github.com/containers/image/v5/directory/explicitfilepath @@ -512,7 +512,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/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706 +# github.com/spf13/cobra v1.1.1 github.com/spf13/cobra # github.com/spf13/pflag v1.0.5 github.com/spf13/pflag