summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile17
-rw-r--r--README.md2
-rw-r--r--cmd/podman/common/create.go8
-rw-r--r--cmd/podman/common/create_opts.go12
-rw-r--r--cmd/podman/completion/completion.go6
-rw-r--r--cmd/podman/containers/create.go16
-rw-r--r--cmd/podman/images/pull.go20
-rw-r--r--cmd/podman/networks/create.go12
-rw-r--r--cmd/podman/networks/list.go27
-rw-r--r--docs/source/markdown/podman-create.1.md5
-rw-r--r--docs/source/markdown/podman-network-create.1.md4
-rw-r--r--docs/source/markdown/podman-network-ls.1.md18
-rw-r--r--docs/source/markdown/podman-pull.1.md5
-rw-r--r--docs/source/markdown/podman-run.1.md5
-rw-r--r--go.mod2
-rw-r--r--go.sum5
-rw-r--r--libpod/network/create.go4
-rw-r--r--libpod/network/files.go30
-rw-r--r--libpod/network/netconflist.go80
-rw-r--r--libpod/runtime_pod_infra_linux.go67
-rw-r--r--pkg/api/handlers/compat/containers_archive.go373
-rw-r--r--pkg/api/handlers/compat/networks.go47
-rw-r--r--pkg/api/handlers/libpod/networks.go4
-rw-r--r--pkg/api/server/register_networks.go15
-rw-r--r--pkg/bindings/network/network.go9
-rw-r--r--pkg/bindings/test/containers_test.go8
-rw-r--r--pkg/domain/entities/network.go8
-rw-r--r--pkg/domain/infra/abi/network.go45
-rw-r--r--test/apiv2/23-containersArchive.at81
-rw-r--r--test/apiv2/35-networks.at19
-rw-r--r--test/e2e/create_test.go32
-rw-r--r--test/e2e/network_test.go59
-rw-r--r--test/e2e/pull_test.go28
-rw-r--r--test/system/010-images.bats6
-rw-r--r--test/system/030-run.bats19
-rw-r--r--test/system/040-ps.bats47
-rw-r--r--test/system/070-build.bats17
-rw-r--r--test/system/075-exec.bats3
-rw-r--r--test/system/200-pod.bats4
-rw-r--r--vendor/github.com/spf13/cobra/bash_completions.go752
-rw-r--r--vendor/github.com/spf13/cobra/custom_completions.go101
-rw-r--r--vendor/github.com/spf13/cobra/fish_completions.go115
-rw-r--r--vendor/github.com/spf13/cobra/zsh_completions.go55
-rw-r--r--vendor/modules.txt2
44 files changed, 1612 insertions, 582 deletions
diff --git a/Makefile b/Makefile
index 3e1401b01..610449e9d 100644
--- a/Makefile
+++ b/Makefile
@@ -460,15 +460,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
@@ -620,14 +611,6 @@ install.libseccomp.sudo:
.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/README.md b/README.md
index 857d43fe8..3c25e53cb 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Podman (the POD MANager) is a tool for managing containers and images, volumes mounted into those containers, and pods made from groups of containers.
Podman is based on libpod, a library for container lifecycle management that is also contained in this repository. The libpod library provides APIs for managing containers, pods, container images, and volumes.
-* [Latest Version: 2.1.1](https://github.com/containers/podman/releases/latest)
+* [Latest Version: 2.2.0](https://github.com/containers/podman/releases/latest)
* Latest Remote client for Windows
* Latest Remote client for MacOs
* Latest Static Remote client for Linux
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 599b430ea..14086ace4 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -513,6 +513,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) {
)
_ = cmd.RegisterFlagCompletionFunc(pidsLimitFlagName, completion.AutocompleteNone)
+ platformFlagName := "platform"
+ createFlags.StringVar(
+ &cf.Platform,
+ platformFlagName, "",
+ "Specify the platform for selecting the image. (Conflicts with override-arch and override-os)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone)
+
podFlagName := "pod"
createFlags.StringVar(
&cf.Pod,
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 6dc43dbc6..af53a3b67 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -78,6 +78,7 @@ type ContainerCLIOpts struct {
OverrideVariant string
PID string
PIDsLimit *int64
+ Platform string
Pod string
PodIDFile string
PreserveFDs uint
@@ -211,12 +212,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
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 +368,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 +382,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 ead8d1f05..84942a508 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/containers/create.go b/cmd/podman/containers/create.go
index db920554e..3d87c71a9 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -18,6 +18,7 @@ import (
"github.com/containers/podman/v2/pkg/specgen"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -236,6 +237,21 @@ func pullImage(imageName string) (string, error) {
imageMissing = !br.Value
}
+ if cliVals.Platform != "" {
+ if cliVals.OverrideArch != "" || cliVals.OverrideOS != "" {
+ return "", errors.Errorf("--platform option can not be specified with --overide-arch or --override-os")
+ }
+ split := strings.SplitN(cliVals.Platform, "/", 2)
+ cliVals.OverrideOS = split[0]
+ if len(split) > 1 {
+ cliVals.OverrideArch = split[1]
+ }
+ if pullPolicy != config.PullImageAlways {
+ logrus.Info("--platform causes the pull policy to be \"always\"")
+ pullPolicy = config.PullImageAlways
+ }
+ }
+
if imageMissing || pullPolicy == config.PullImageAlways {
if pullPolicy == config.PullImageNever {
return "", errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", imageName)
diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go
index a6f41688c..f8e1ee226 100644
--- a/cmd/podman/images/pull.go
+++ b/cmd/podman/images/pull.go
@@ -3,6 +3,7 @@ package images
import (
"fmt"
"os"
+ "strings"
"github.com/containers/common/pkg/auth"
"github.com/containers/common/pkg/completion"
@@ -11,6 +12,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -94,6 +96,10 @@ func pullFlags(cmd *cobra.Command) {
flags.StringVar(&pullOptions.OverrideVariant, overrideVariantFlagName, "", " use VARIANT instead of the running architecture variant for choosing images")
_ = cmd.RegisterFlagCompletionFunc(overrideVariantFlagName, completion.AutocompleteNone)
+ platformFlagName := "platform"
+ flags.String(platformFlagName, "", "Specify the platform for selecting the image. (Conflicts with override-arch and override-os)")
+ _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone)
+
flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP")
flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images")
flags.StringVar(&pullOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
@@ -127,6 +133,20 @@ func imagePull(cmd *cobra.Command, args []string) error {
return err
}
}
+ platform, err := cmd.Flags().GetString("platform")
+ if err != nil {
+ return err
+ }
+ if platform != "" {
+ if pullOptions.OverrideArch != "" || pullOptions.OverrideOS != "" {
+ return errors.Errorf("--platform option can not be specified with --overide-arch or --override-os")
+ }
+ split := strings.SplitN(platform, "/", 2)
+ pullOptions.OverrideOS = split[0]
+ if len(split) > 1 {
+ pullOptions.OverrideArch = split[1]
+ }
+ }
if pullOptions.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(pullOptions.CredentialsCLI)
diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go
index 17de2c95d..938b8da77 100644
--- a/cmd/podman/networks/create.go
+++ b/cmd/podman/networks/create.go
@@ -6,9 +6,11 @@ import (
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v2/cmd/podman/common"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -27,6 +29,7 @@ var (
var (
networkCreateOptions entities.NetworkCreateOptions
+ labels []string
)
func networkCreateFlags(cmd *cobra.Command) {
@@ -50,6 +53,10 @@ func networkCreateFlags(cmd *cobra.Command) {
flags.StringVar(&networkCreateOptions.MacVLAN, macvlanFlagName, "", "create a Macvlan connection based on this device")
_ = cmd.RegisterFlagCompletionFunc(macvlanFlagName, completion.AutocompleteNone)
+ labelFlagName := "label"
+ flags.StringArrayVar(&labels, labelFlagName, nil, "set metadata on a network")
+ _ = cmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone)
+
// TODO not supported yet
// flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
@@ -81,6 +88,11 @@ func networkCreate(cmd *cobra.Command, args []string) error {
}
name = args[0]
}
+ var err error
+ networkCreateOptions.Labels, err = parse.GetAllLabels([]string{}, labels)
+ if err != nil {
+ return errors.Wrap(err, "failed to parse labels")
+ }
response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions)
if err != nil {
return err
diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go
index dcba3f186..6e6bbb07d 100644
--- a/cmd/podman/networks/list.go
+++ b/cmd/podman/networks/list.go
@@ -16,6 +16,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -35,17 +36,18 @@ var (
var (
networkListOptions entities.NetworkListOptions
+ filters []string
)
func networkListFlags(flags *pflag.FlagSet) {
formatFlagName := "format"
- flags.StringVarP(&networkListOptions.Format, formatFlagName, "f", "", "Pretty-print networks to JSON or using a Go template")
+ flags.StringVar(&networkListOptions.Format, formatFlagName, "", "Pretty-print networks to JSON or using a Go template")
_ = networklistCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat)
flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names")
filterFlagName := "filter"
- flags.StringVarP(&networkListOptions.Filter, filterFlagName, "", "", "Provide filter values (e.g. 'name=podman')")
+ flags.StringArrayVarP(&filters, filterFlagName, "f", nil, "Provide filter values (e.g. 'name=podman')")
_ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteNetworkFilters)
}
@@ -61,14 +63,14 @@ func init() {
}
func networkList(cmd *cobra.Command, args []string) error {
- // validate the filter pattern.
- if len(networkListOptions.Filter) > 0 {
- tokens := strings.Split(networkListOptions.Filter, "=")
- if len(tokens) != 2 {
- return fmt.Errorf("invalid filter syntax : %s", networkListOptions.Filter)
+ networkListOptions.Filters = make(map[string][]string)
+ for _, f := range filters {
+ split := strings.SplitN(f, "=", 2)
+ if len(split) == 1 {
+ return errors.Errorf("invalid filter %q", f)
}
+ networkListOptions.Filters[split[0]] = append(networkListOptions.Filters[split[0]], split[1])
}
-
responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil {
return err
@@ -93,6 +95,7 @@ func networkList(cmd *cobra.Command, args []string) error {
"CNIVersion": "version",
"Version": "version",
"Plugins": "plugins",
+ "Labels": "labels",
})
renderHeaders := true
row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n"
@@ -144,3 +147,11 @@ func (n ListPrintReports) Version() string {
func (n ListPrintReports) Plugins() string {
return network.GetCNIPlugins(n.NetworkConfigList)
}
+
+func (n ListPrintReports) Labels() string {
+ list := make([]string, 0, len(n.NetworkListReport.Labels))
+ for k, v := range n.NetworkListReport.Labels {
+ list = append(list, k+"="+v)
+ }
+ return strings.Join(list, ",")
+}
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index ab390447e..843e2b22f 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -689,6 +689,11 @@ Default is to create a private PID namespace for the container
Tune the container's pids limit. Set `0` to have unlimited pids for the container. (default "4096" on systems that support PIDS cgroups).
+#### **--platform**=*OS/ARCH*
+
+Specify the platform for selecting the image. (Conflicts with override-arch and override-os)
+The `--platform` option can be used to override the current architecture and operating system.
+
#### **--pod**=*name*
Run container in an existing pod. If you want Podman to make the pod for you, preference the pod name with `new:`.
diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md
index cbf9d26dc..d787809cd 100644
--- a/docs/source/markdown/podman-network-create.1.md
+++ b/docs/source/markdown/podman-network-create.1.md
@@ -40,6 +40,10 @@ Restrict external access of this network
Allocate container IP from a range. The range must be a complete subnet and in CIDR notation. The *ip-range* option
must be used with a *subnet* option.
+#### **--label**
+
+Set metadata for a network (e.g., --label mykey=value).
+
#### **--macvlan**
Create a *Macvlan* based connection rather than a classic bridge. You must pass an interface name from the host for the
diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md
index 34b40b3ae..fcba51190 100644
--- a/docs/source/markdown/podman-network-ls.1.md
+++ b/docs/source/markdown/podman-network-ls.1.md
@@ -14,13 +14,25 @@ Displays a list of existing podman networks. This command is not available for r
The `quiet` option will restrict the output to only the network names.
-#### **--format**, **-f**
+#### **--format**
Pretty-print networks to JSON or using a Go template.
-#### **--filter**
+#### **--filter**, **-f**
-Provide filter values (e.g. 'name=podman').
+Filter output based on conditions given.
+Multiple filters can be given with multiple uses of the --filter flag.
+Filters with the same key work inclusive with the only exception being
+`label` which is exclusive. Filters with different keys always work exclusive.
+
+Valid filters are listed below:
+
+| **Filter** | **Description** |
+| ---------- | ------------------------------------------------------------------------------------- |
+| name | [Name] Network name (accepts regex) |
+| label | [Key] or [Key=Value] Label assigned to a network |
+| plugin | [Plugin] CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`) |
+| driver | [Driver] Only `bridge` is supported |
## EXAMPLE
diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md
index 4495cec3a..44a7e83b6 100644
--- a/docs/source/markdown/podman-pull.1.md
+++ b/docs/source/markdown/podman-pull.1.md
@@ -106,6 +106,11 @@ Override the OS, defaults to hosts, of the image to be pulled. For example, `win
Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7.
+#### **--platform**=*OS/ARCH*
+
+Specify the platform for selecting the image. (Conflicts with override-arch and override-os)
+The `--platform` option can be used to override the current architecture and operating system.
+
#### **--quiet**, **-q**
Suppress output information when pulling images
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 13b586e17..83aaa33e8 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -717,6 +717,11 @@ The efault is to create a private PID namespace for the container.
Tune the container's pids limit. Set to **0** to have unlimited pids for the container. The default is **4096** on systems that support "pids" cgroup controller.
+#### **--platform**=*OS/ARCH*
+
+Specify the platform for selecting the image. (Conflicts with override-arch and override-os)
+The `--platform` option can be used to override the current architecture and operating system.
+
#### **--pod**=*name*
Run container in an existing pod. If you want Podman to make the pod for you, prefix the pod name with **new:**.
diff --git a/go.mod b/go.mod
index 153e22f6f..e44c5843d 100644
--- a/go.mod
+++ b/go.mod
@@ -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 8460fc745..8c588de4d 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=
@@ -502,6 +500,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/network/create.go b/libpod/network/create.go
index 7e4fc574a..cac438963 100644
--- a/libpod/network/create.go
+++ b/libpod/network/create.go
@@ -169,7 +169,7 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon
}
// create CNI plugin configuration
- ncList := NewNcList(name, version.Current())
+ ncList := NewNcList(name, version.Current(), options.Labels)
var plugins []CNIPlugins
// TODO need to iron out the role of isDefaultGW and IPMasq
bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
@@ -223,7 +223,7 @@ func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeCo
return "", err
}
}
- ncList := NewNcList(name, version.Current())
+ ncList := NewNcList(name, version.Current(), options.Labels)
macvlan := NewMacVLANPlugin(options.MacVLAN)
plugins = append(plugins, macvlan)
ncList["plugins"] = plugins
diff --git a/libpod/network/files.go b/libpod/network/files.go
index 7f1e3ee18..83cb1c23a 100644
--- a/libpod/network/files.go
+++ b/libpod/network/files.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/libpod/define"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// ErrNoSuchNetworkInterface indicates that no network interface exists
@@ -89,6 +90,35 @@ func GetCNIPlugins(list *libcni.NetworkConfigList) string {
return strings.Join(plugins, ",")
}
+// GetNetworkLabels returns a list of labels as a string
+func GetNetworkLabels(list *libcni.NetworkConfigList) NcLabels {
+ cniJSON := make(map[string]interface{})
+ err := json.Unmarshal(list.Bytes, &cniJSON)
+ if err != nil {
+ logrus.Errorf("failed to unmarshal network config %v %v", cniJSON["name"], err)
+ return nil
+ }
+ if args, ok := cniJSON["args"]; ok {
+ if key, ok := args.(map[string]interface{}); ok {
+ if labels, ok := key[PodmanLabelKey]; ok {
+ if labels, ok := labels.(map[string]interface{}); ok {
+ result := make(NcLabels, len(labels))
+ for k, v := range labels {
+ if v, ok := v.(string); ok {
+ result[k] = v
+ } else {
+ logrus.Errorf("network config %v invalid label value type %T should be string", cniJSON["name"], labels)
+ }
+ }
+ return result
+ }
+ logrus.Errorf("network config %v invalid label type %T should be map[string]string", cniJSON["name"], labels)
+ }
+ }
+ }
+ return nil
+}
+
// GetNetworksFromFilesystem gets all the networks from the cni configuration
// files
func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) {
diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go
index ee9adce14..3db38485b 100644
--- a/libpod/network/netconflist.go
+++ b/libpod/network/netconflist.go
@@ -4,6 +4,11 @@ import (
"net"
"os"
"path/filepath"
+ "strings"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
)
const (
@@ -14,12 +19,24 @@ const (
// NcList describes a generic map
type NcList map[string]interface{}
+// NcArgs describes the cni args field
+type NcArgs map[string]NcLabels
+
+// NcLabels describes the label map
+type NcLabels map[string]string
+
+// PodmanLabelKey key used to store the podman network label in a cni config
+const PodmanLabelKey = "podman_labels"
+
// NewNcList creates a generic map of values with string
// keys and adds in version and network name
-func NewNcList(name, version string) NcList {
+func NewNcList(name, version string, labels NcLabels) NcList {
n := NcList{}
n["cniVersion"] = version
n["name"] = name
+ if len(labels) > 0 {
+ n["args"] = NcArgs{PodmanLabelKey: labels}
+ }
return n
}
@@ -159,3 +176,64 @@ func NewMacVLANPlugin(device string) MacVLANConfig {
}
return m
}
+
+// IfPassesFilter filters NetworkListReport and returns true if the filter match the given config
+func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]string) (bool, error) {
+ result := true
+ for key, filterValues := range filters {
+ result = false
+ switch strings.ToLower(key) {
+ case "name":
+ // matches one name, regex allowed
+ result = util.StringMatchRegexSlice(netconf.Name, filterValues)
+
+ case "plugin":
+ // match one plugin
+ plugins := GetCNIPlugins(netconf)
+ for _, val := range filterValues {
+ if strings.Contains(plugins, val) {
+ result = true
+ break
+ }
+ }
+
+ case "label":
+ // matches all labels
+ labels := GetNetworkLabels(netconf)
+ outer:
+ for _, filterValue := range filterValues {
+ filterArray := strings.SplitN(filterValue, "=", 2)
+ filterKey := filterArray[0]
+ if len(filterArray) > 1 {
+ filterValue = filterArray[1]
+ } else {
+ filterValue = ""
+ }
+ for labelKey, labelValue := range labels {
+ if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
+ result = true
+ continue outer
+ }
+ }
+ result = false
+ }
+
+ case "driver":
+ // matches only for the DefaultNetworkDriver
+ for _, filterValue := range filterValues {
+ plugins := GetCNIPlugins(netconf)
+ if filterValue == DefaultNetworkDriver &&
+ strings.Contains(plugins, DefaultNetworkDriver) {
+ result = true
+ }
+ }
+
+ // TODO: add dangling filter
+ // TODO TODO: add id filter if we support ids
+
+ default:
+ return false, errors.Errorf("invalid filter %q", key)
+ }
+ }
+ return result, nil
+}
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index 76419587a..3e4185db1 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -34,40 +34,56 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
// Set Pod hostname
g.Config.Hostname = p.config.Hostname
+ var options []CtrCreateOption
+
+ // Command: If user-specified, use that preferentially.
+ // If not set and the config file is set, fall back to that.
+ var infraCtrCommand []string
+ if p.config.InfraContainer.InfraCommand != nil {
+ logrus.Debugf("User-specified infra container entrypoint %v", p.config.InfraContainer.InfraCommand)
+ infraCtrCommand = p.config.InfraContainer.InfraCommand
+ } else if r.config.Engine.InfraCommand != "" {
+ logrus.Debugf("Config-specified infra container entrypoint %s", r.config.Engine.InfraCommand)
+ infraCtrCommand = []string{r.config.Engine.InfraCommand}
+ }
+ // Only if set by the user or containers.conf, we set entrypoint for the
+ // infra container.
+ // This is only used by commit, so it shouldn't matter... But someone
+ // may eventually want to commit an infra container?
+ // TODO: Should we actually do this if set by containers.conf?
+ if infraCtrCommand != nil {
+ // Need to duplicate the array - we are going to add Cmd later
+ // so the current array will be changed.
+ newArr := make([]string, 0, len(infraCtrCommand))
+ newArr = append(newArr, infraCtrCommand...)
+ options = append(options, WithEntrypoint(newArr))
+ }
+
isRootless := rootless.IsRootless()
- entrypointSet := len(p.config.InfraContainer.InfraCommand) > 0
- entryPoint := p.config.InfraContainer.InfraCommand
- entryCmd := []string{}
- var options []CtrCreateOption
// I've seen circumstances where config is being passed as nil.
// Let's err on the side of safety and make sure it's safe to use.
if config != nil {
- // default to entrypoint in image if there is one
- if !entrypointSet {
- if len(config.Entrypoint) > 0 {
- entrypointSet = true
- entryPoint = config.Entrypoint
- entryCmd = config.Entrypoint
+ if infraCtrCommand == nil {
+ // If we have no entrypoint and command from the image,
+ // we can't go on - the infra container has no command.
+ if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
+ return nil, errors.Errorf("infra container has no command")
}
- } else { // so use the InfraCommand
- entrypointSet = true
- entryCmd = entryPoint
- }
-
- if len(config.Cmd) > 0 {
- // We can't use the default pause command, since we're
- // sourcing from the image. If we didn't already set an
- // entrypoint, set one now.
- if !entrypointSet {
+ if len(config.Entrypoint) > 0 {
+ infraCtrCommand = config.Entrypoint
+ } else {
// Use the Docker default "/bin/sh -c"
// entrypoint, as we're overriding command.
// If an image doesn't want this, it can
// override entrypoint too.
- entryCmd = []string{"/bin/sh", "-c"}
+ infraCtrCommand = []string{"/bin/sh", "-c"}
}
- entryCmd = append(entryCmd, config.Cmd...)
}
+ if len(config.Cmd) > 0 {
+ infraCtrCommand = append(infraCtrCommand, config.Cmd...)
+ }
+
if len(config.Env) > 0 {
for _, nameValPair := range config.Env {
nameValSlice := strings.Split(nameValPair, "=")
@@ -127,9 +143,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
}
g.SetRootReadonly(true)
- g.SetProcessArgs(entryCmd)
+ g.SetProcessArgs(infraCtrCommand)
- logrus.Debugf("Using %q as infra container entrypoint", entryCmd)
+ logrus.Debugf("Using %q as infra container command", infraCtrCommand)
g.RemoveMount("/dev/shm")
if isRootless {
@@ -148,9 +164,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
options = append(options, WithRootFSFromImage(imgID, imgName, rawImageName))
options = append(options, WithName(containerName))
options = append(options, withIsInfra())
- if entrypointSet {
- options = append(options, WithEntrypoint(entryPoint))
- }
if len(p.config.InfraContainer.ConmonPidFile) > 0 {
options = append(options, WithConmonPidFile(p.config.InfraContainer.ConmonPidFile))
}
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go
index 293a17e0f..1dd563393 100644
--- a/pkg/api/handlers/compat/containers_archive.go
+++ b/pkg/api/handlers/compat/containers_archive.go
@@ -1,12 +1,379 @@
package compat
import (
- "errors"
- "net/http"
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "path/filepath"
+ "strings"
+ "github.com/containers/buildah/copier"
+ "github.com/containers/buildah/pkg/chrootuser"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/storage/pkg/idtools"
+ "github.com/opencontainers/runtime-spec/specs-go"
+
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func Archive(w http.ResponseWriter, r *http.Request) {
- utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ switch r.Method {
+ case http.MethodPut:
+ handlePut(w, r, decoder, runtime)
+ case http.MethodGet, http.MethodHead:
+ handleHeadOrGet(w, r, decoder, runtime)
+ default:
+ utils.Error(w, fmt.Sprintf("not implemented, method: %v", r.Method), http.StatusNotImplemented, errors.New(fmt.Sprintf("not implemented, method: %v", r.Method)))
+ }
+}
+
+func handleHeadOrGet(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+ query := struct {
+ Path string `schema:"path"`
+ }{}
+
+ err := decoder.Decode(&query, r.URL.Query())
+ if err != nil {
+ utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
+ return
+ }
+
+ if query.Path == "" {
+ utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.New("missing `path` parameter"))
+ return
+ }
+
+ containerName := utils.GetName(r)
+
+ ctr, err := runtime.LookupContainer(containerName)
+ if errors.Cause(err) == define.ErrNoSuchCtr {
+ utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrap(err, "the container doesn't exists"))
+ return
+ } else if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ mountPoint, err := ctr.Mount()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to mount the container"))
+ return
+ }
+
+ defer func() {
+ if err := ctr.Unmount(false); err != nil {
+ logrus.Warnf("failed to unmount container %s: %q", containerName, err)
+ }
+ }()
+
+ opts := copier.StatOptions{}
+
+ mountPoint, path, err := fixUpMountPointAndPath(runtime, ctr, mountPoint, query.Path)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ stats, err := copier.Stat(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)})
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to get stats about file"))
+ return
+ }
+
+ if len(stats) <= 0 || len(stats[0].Globbed) <= 0 {
+ errs := make([]string, 0, len(stats))
+
+ for _, stat := range stats {
+ if stat.Error != "" {
+ errs = append(errs, stat.Error)
+ }
+ }
+
+ utils.Error(w, "Not found.", http.StatusNotFound, fmt.Errorf("file doesn't exist (errs: %q)", strings.Join(errs, ";")))
+
+ return
+ }
+
+ statHeader, err := statsToHeader(stats[0].Results[stats[0].Globbed[0]])
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ w.Header().Add("X-Docker-Container-Path-Stat", statHeader)
+
+ if r.Method == http.MethodGet {
+ idMappingOpts, err := ctr.IDMappings()
+ if err != nil {
+ utils.Error(w, "Not found.", http.StatusInternalServerError,
+ errors.Wrapf(err, "error getting IDMappingOptions"))
+ return
+ }
+
+ destOwner := idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
+
+ opts := copier.GetOptions{
+ UIDMap: idMappingOpts.UIDMap,
+ GIDMap: idMappingOpts.GIDMap,
+ ChownDirs: &destOwner,
+ ChownFiles: &destOwner,
+ KeepDirectoryNames: true,
+ }
+
+ w.WriteHeader(http.StatusOK)
+
+ err = copier.Get(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)}, w)
+ if err != nil {
+ logrus.Error(errors.Wrapf(err, "failed to copy from the %s container path %s", containerName, query.Path))
+ return
+ }
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+}
+
+func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+ query := struct {
+ Path string `schema:"path"`
+ // TODO handle params below
+ NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
+ CopyUIDGID bool `schema:"copyUIDGID"`
+ }{}
+
+ err := decoder.Decode(&query, r.URL.Query())
+ if err != nil {
+ utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
+ return
+ }
+
+ ctrName := utils.GetName(r)
+
+ ctr, err := runtime.LookupContainer(ctrName)
+ if err != nil {
+ utils.Error(w, "Not found", http.StatusNotFound, errors.Wrapf(err, "the %s container doesn't exists", ctrName))
+ return
+ }
+
+ mountPoint, err := ctr.Mount()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, errors.Wrapf(err, "failed to mount the %s container", ctrName))
+ return
+ }
+
+ defer func() {
+ if err := ctr.Unmount(false); err != nil {
+ logrus.Warnf("failed to unmount container %s", ctrName)
+ }
+ }()
+
+ user, err := getUser(mountPoint, ctr.User())
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+
+ idMappingOpts, err := ctr.IDMappings()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, errors.Wrapf(err, "error getting IDMappingOptions"))
+ return
+ }
+
+ destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
+
+ opts := copier.PutOptions{
+ UIDMap: idMappingOpts.UIDMap,
+ GIDMap: idMappingOpts.GIDMap,
+ ChownDirs: &destOwner,
+ ChownFiles: &destOwner,
+ }
+
+ mountPoint, path, err := fixUpMountPointAndPath(runtime, ctr, mountPoint, query.Path)
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+
+ err = copier.Put(mountPoint, filepath.Join(mountPoint, path), opts, r.Body)
+ if err != nil {
+ logrus.Error(errors.Wrapf(err, "failed to copy to the %s container path %s", ctrName, query.Path))
+ return
+ }
+}
+
+func statsToHeader(stats *copier.StatForItem) (string, error) {
+ statsDTO := struct {
+ Name string `json:"name"`
+ Size int64 `json:"size"`
+ Mode os.FileMode `json:"mode"`
+ ModTime time.Time `json:"mtime"`
+ LinkTarget string `json:"linkTarget"`
+ }{
+ Name: filepath.Base(stats.Name),
+ Size: stats.Size,
+ Mode: stats.Mode,
+ ModTime: stats.ModTime,
+ LinkTarget: stats.ImmediateTarget,
+ }
+
+ jsonBytes, err := json.Marshal(&statsDTO)
+ if err != nil {
+ return "", errors.Wrap(err, "failed to serialize file stats")
+ }
+
+ buff := bytes.NewBuffer(make([]byte, 0, 128))
+ base64encoder := base64.NewEncoder(base64.StdEncoding, buff)
+
+ _, err = base64encoder.Write(jsonBytes)
+ if err != nil {
+ return "", err
+ }
+
+ err = base64encoder.Close()
+ if err != nil {
+ return "", err
+ }
+
+ return buff.String(), nil
+}
+
+// the utility functions below are copied from abi/cp.go
+
+func getUser(mountPoint string, userspec string) (specs.User, error) {
+ uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
+ u := specs.User{
+ UID: uid,
+ GID: gid,
+ Username: userspec,
+ }
+
+ if !strings.Contains(userspec, ":") {
+ groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
+ if err2 != nil {
+ if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
+ err = err2
+ }
+ } else {
+ u.AdditionalGids = groups
+ }
+ }
+
+ return u, err
+}
+
+func fixUpMountPointAndPath(runtime *libpod.Runtime, ctr *libpod.Container, mountPoint, ctrPath string) (string, string, error) {
+ if !filepath.IsAbs(ctrPath) {
+ endsWithSep := strings.HasSuffix(ctrPath, string(filepath.Separator))
+ ctrPath = filepath.Join(ctr.WorkingDir(), ctrPath)
+
+ if endsWithSep {
+ ctrPath = ctrPath + string(filepath.Separator)
+ }
+ }
+ if isVol, volDestName, volName := isVolumeDestName(ctrPath, ctr); isVol { //nolint(gocritic)
+ newMountPoint, path, err := pathWithVolumeMount(runtime, volDestName, volName, ctrPath)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error getting source path from volume %s", volDestName)
+ }
+
+ mountPoint = newMountPoint
+ ctrPath = path
+ } else if isBindMount, mount := isBindMountDestName(ctrPath, ctr); isBindMount { //nolint(gocritic)
+ newMountPoint, path := pathWithBindMountSource(mount, ctrPath)
+ mountPoint = newMountPoint
+ ctrPath = path
+ }
+
+ return mountPoint, ctrPath, nil
+}
+
+func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
+ separator := string(os.PathSeparator)
+
+ if filepath.IsAbs(path) {
+ path = strings.TrimPrefix(path, separator)
+ }
+
+ if path == "" {
+ return false, "", ""
+ }
+
+ for _, vol := range ctr.Config().NamedVolumes {
+ volNamePath := strings.TrimPrefix(vol.Dest, separator)
+ if matchVolumePath(path, volNamePath) {
+ return true, vol.Dest, vol.Name
+ }
+ }
+
+ return false, "", ""
+}
+
+func pathWithVolumeMount(runtime *libpod.Runtime, volDestName, volName, path string) (string, string, error) {
+ destVolume, err := runtime.GetVolume(volName)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error getting volume destination %s", volName)
+ }
+
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(string(os.PathSeparator), path)
+ }
+
+ return destVolume.MountPoint(), strings.TrimPrefix(path, volDestName), err
+}
+
+func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
+ separator := string(os.PathSeparator)
+
+ if filepath.IsAbs(path) {
+ path = strings.TrimPrefix(path, string(os.PathSeparator))
+ }
+
+ if path == "" {
+ return false, specs.Mount{}
+ }
+
+ for _, m := range ctr.Config().Spec.Mounts {
+ if m.Type != "bind" {
+ continue
+ }
+
+ mDest := strings.TrimPrefix(m.Destination, separator)
+ if matchVolumePath(path, mDest) {
+ return true, m
+ }
+ }
+
+ return false, specs.Mount{}
+}
+
+func matchVolumePath(path, target string) bool {
+ pathStr := filepath.Clean(path)
+ target = filepath.Clean(target)
+
+ for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
+ pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
+ }
+
+ return pathStr == target
+}
+
+func pathWithBindMountSource(m specs.Mount, path string) (string, string) {
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(string(os.PathSeparator), path)
+ }
+
+ return m.Source, strings.TrimPrefix(path, m.Destination)
}
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index c74cdb840..762f88a68 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -50,7 +50,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.NetworkNotFound(w, name, err)
return
}
- report, err := getNetworkResourceByName(name, runtime)
+ report, err := getNetworkResourceByName(name, runtime, nil)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -58,7 +58,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, report)
}
-func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) {
+func getNetworkResourceByName(name string, runtime *libpod.Runtime, filters map[string][]string) (*types.NetworkResource, error) {
var (
ipamConfigs []dockerNetwork.IPAMConfig
)
@@ -85,6 +85,16 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
if err != nil {
return nil, err
}
+ if len(filters) > 0 {
+ ok, err := network.IfPassesFilter(conf, filters)
+ if err != nil {
+ return nil, err
+ }
+ if !ok {
+ // do not return the config if we did not match the filter
+ return nil, nil
+ }
+ }
// No Bridge plugin means we bail
bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
@@ -129,14 +139,14 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
Options: nil,
Config: ipamConfigs,
},
- Internal: false,
+ Internal: !bridge.IsGW,
Attachable: false,
Ingress: false,
ConfigFrom: dockerNetwork.ConfigReference{},
ConfigOnly: false,
Containers: containerEndpoints,
Options: nil,
- Labels: nil,
+ Labels: network.GetNetworkLabels(conf),
Peers: nil,
Services: nil,
}
@@ -180,41 +190,23 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
return
}
- filterNames, nameFilterExists := query.Filters["name"]
- // TODO remove when filters are implemented
- if (!nameFilterExists && len(query.Filters) > 0) || len(query.Filters) > 1 {
- utils.InternalServerError(w, errors.New("only the name filter for listing networks is implemented"))
- return
- }
netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil {
utils.InternalServerError(w, err)
return
}
- // filter by name
- if nameFilterExists {
- names := []string{}
- for _, name := range netNames {
- for _, filter := range filterNames {
- if strings.Contains(name, filter) {
- names = append(names, name)
- break
- }
- }
- }
- netNames = names
- }
-
- reports := make([]*types.NetworkResource, 0, len(netNames))
+ var reports []*types.NetworkResource
logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames {
- report, err := getNetworkResourceByName(name, runtime)
+ report, err := getNetworkResourceByName(name, runtime, query.Filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
- reports = append(reports, report)
+ if report != nil {
+ reports = append(reports, report)
+ }
}
utils.WriteResponse(w, http.StatusOK, reports)
}
@@ -245,6 +237,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
ncOptions := entities.NetworkCreateOptions{
Driver: network.DefaultNetworkDriver,
Internal: networkCreate.Internal,
+ Labels: networkCreate.Labels,
}
if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
if len(networkCreate.IPAM.Config) > 1 {
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index f1578f829..8511e2733 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -48,7 +48,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
- Filter string `schema:"filter"`
+ Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
@@ -59,7 +59,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
}
options := entities.NetworkListOptions{
- Filter: query.Filter,
+ Filters: query.Filters,
}
ic := abi.ContainerEngine{Libpod: runtime}
reports, err := ic.NetworkList(r.Context(), options)
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index ea169cbdf..193b05e6d 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -65,7 +65,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// - in: query
// name: filters
// type: string
- // description: JSON encoded value of the filters (a map[string][]string) to process on the networks list. Only the name filter is supported.
+ // description: |
+ // JSON encoded value of the filters (a map[string][]string) to process on the network list. Currently available filters:
+ // - name=[name] Matches network name (accepts regex).
+ // - driver=[driver] Only bridge is supported.
+ // - label=[key] or label=[key=value] Matches networks based on the presence of a label alone or a label and a value.
// produces:
// - application/json
// responses:
@@ -216,9 +220,14 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// description: Display summary of network configurations
// parameters:
// - in: query
- // name: filter
+ // name: filters
// type: string
- // description: Provide filter values (e.g. 'name=podman')
+ // description: |
+ // JSON encoded value of the filters (a map[string][]string) to process on the network list. Available filters:
+ // - name=[name] Matches network name (accepts regex).
+ // - driver=[driver] Only bridge is supported.
+ // - label=[key] or label=[key=value] Matches networks based on the presence of a label alone or a label and a value.
+ // - plugin=[plugin] Matches CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`)
// produces:
// - application/json
// responses:
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index 1d4be8a4c..347f97703 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -2,6 +2,7 @@ package network
import (
"context"
+ "encoding/json"
"net/http"
"net/url"
"strconv"
@@ -79,8 +80,12 @@ func List(ctx context.Context, options entities.NetworkListOptions) ([]*entities
return nil, err
}
params := url.Values{}
- if options.Filter != "" {
- params.Set("filter", options.Filter)
+ if options.Filters != nil {
+ b, err := json.Marshal(options.Filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", string(b))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", params, nil)
if err != nil {
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index 0fb677768..15066ff1a 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -290,17 +290,17 @@ var _ = Describe("Podman containers ", func() {
Expect(wait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", -1))
- errChan = make(chan error)
+ unpauseErrChan := make(chan error)
go func() {
defer GinkgoRecover()
_, waitErr := containers.Wait(bt.conn, name, &running)
- errChan <- waitErr
- close(errChan)
+ unpauseErrChan <- waitErr
+ close(unpauseErrChan)
}()
err = containers.Unpause(bt.conn, name)
Expect(err).To(BeNil())
- unPausewait := <-errChan
+ unPausewait := <-unpauseErrChan
Expect(unPausewait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", -1))
})
diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go
index 86c2e1bcd..f14cac7ef 100644
--- a/pkg/domain/entities/network.go
+++ b/pkg/domain/entities/network.go
@@ -8,14 +8,15 @@ import (
// NetworkListOptions describes options for listing networks in cli
type NetworkListOptions struct {
- Format string
- Quiet bool
- Filter string
+ Format string
+ Quiet bool
+ Filters map[string][]string
}
// NetworkListReport describes the results from listing networks
type NetworkListReport struct {
*libcni.NetworkConfigList
+ Labels map[string]string
}
// NetworkInspectReport describes the results from inspect networks
@@ -39,6 +40,7 @@ type NetworkCreateOptions struct {
Driver string
Gateway net.IP
Internal bool
+ Labels map[string]string
MacVLAN string
Range net.IPNet
Subnet net.IPNet
diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go
index c52584565..6a219edd5 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -2,10 +2,7 @@ package abi
import (
"context"
- "fmt"
- "strings"
- "github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -26,18 +23,16 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net
return nil, err
}
- var tokens []string
- // tokenize the networkListOptions.Filter in key=value.
- if len(options.Filter) > 0 {
- tokens = strings.Split(options.Filter, "=")
- if len(tokens) != 2 {
- return nil, fmt.Errorf("invalid filter syntax : %s", options.Filter)
- }
- }
-
for _, n := range networks {
- if ifPassesFilterTest(n, tokens) {
- reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n})
+ ok, err := network.IfPassesFilter(n, options.Filters)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ reports = append(reports, &entities.NetworkListReport{
+ NetworkConfigList: n,
+ Labels: network.GetNetworkLabels(n),
+ })
}
}
return reports, nil
@@ -117,28 +112,6 @@ func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, optio
return network.Create(name, options, runtimeConfig)
}
-func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool {
- result := false
- if len(filter) == 0 {
- // No filter, so pass
- return true
- }
- switch strings.ToLower(filter[0]) {
- case "name":
- if filter[1] == netconf.Name {
- result = true
- }
- case "plugin":
- plugins := network.GetCNIPlugins(netconf)
- if strings.Contains(plugins, filter[1]) {
- result = true
- }
- default:
- result = false
- }
- return result
-}
-
// NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force)
diff --git a/test/apiv2/23-containersArchive.at b/test/apiv2/23-containersArchive.at
new file mode 100644
index 000000000..459800196
--- /dev/null
+++ b/test/apiv2/23-containersArchive.at
@@ -0,0 +1,81 @@
+# -*- sh -*-
+#
+# test more container-related endpoints
+#
+
+red='\e[31m'
+nc='\e[0m'
+
+podman pull $IMAGE &>/dev/null
+
+# Ensure clean slate
+podman rm -a -f &>/dev/null
+
+CTR="ArchiveTestingCtr"
+
+TMPD=$(mktemp -d)
+pushd "${TMPD}"
+echo "Hello" > "hello.txt"
+tar --format=posix -cvf "hello.tar" "hello.txt" &> /dev/null
+popd
+
+HELLO_TAR="${TMPD}/hello.tar"
+
+podman run -d --name "${CTR}" "${IMAGE}" top
+
+function cleanUpArchiveTest() {
+ podman container stop "${CTR}" &> /dev/null
+ podman container rm "${CTR}" &> /dev/null
+ rm -fr "${TMPD}" &> /dev/null
+}
+
+t HEAD "containers/nonExistentCtr/archive?path=%2F" 404
+t HEAD "containers/${CTR}/archive?path=%2Fnon%2Fexistent%2Fpath" 404
+t HEAD "containers/${CTR}/archive?path=%2Fetc%2Fpasswd" 200
+
+curl "http://$HOST:$PORT/containers/${CTR}/archive?path=%2Ftmp%2F" \
+ -X PUT \
+ -H "Content-Type: application/x-tar" \
+ --upload-file "${HELLO_TAR}" &> /dev/null
+
+if ! podman exec -it "${CTR}" "grep" "Hello" "/tmp/hello.txt" &> /dev/null ; then
+ echo -e "${red}NOK: The hello.txt file has not been uploaded.${nc}" 1>&2;
+ cleanUpArchiveTest
+ exit 1
+fi
+
+t HEAD "containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" 200
+
+curl "http://$HOST:$PORT/containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" \
+ --dump-header "${TMPD}/headers.txt" \
+ -o "${TMPD}/body.tar" \
+ -X GET &> /dev/null
+
+PATH_STAT="$(grep X-Docker-Container-Path-Stat "${TMPD}/headers.txt" | cut -d " " -f 2 | base64 -d --ignore-garbage)"
+
+ARCHIVE_TEST_ERROR=""
+
+if [ "$(echo "${PATH_STAT}" | jq ".name")" != '"hello.txt"' ]; then
+ echo -e "${red}NOK: Wrong name in X-Docker-Container-Path-Stat header.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+if [ "$(echo "${PATH_STAT}" | jq ".size")" != "6" ]; then
+ echo -e "${red}NOK: Wrong size in X-Docker-Container-Path-Stat header.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+if ! tar -tf "${TMPD}/body.tar" | grep "hello.txt" &> /dev/null; then
+ echo -e "${red}NOK: Body doesn't contain expected file.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+if [ "$(tar -xf "${TMPD}/body.tar" hello.txt --to-stdout)" != "Hello" ]; then
+ echo -e "${red}NOK: Content of file doesn't match.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+cleanUpArchiveTest
+if [[ "${ARCHIVE_TEST_ERROR}" ]] ; then
+ exit 1;
+fi
diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at
index ad34511c7..d9556d59f 100644
--- a/test/apiv2/35-networks.at
+++ b/test/apiv2/35-networks.at
@@ -9,8 +9,8 @@ t GET networks/non-existing-network 404 \
t POST libpod/networks/create?name=network1 '' 200 \
.Filename~.*/network1\\.conflist
-# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}}'
-t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}' 200 \
+# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}}'
+t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}' 200 \
.Filename~.*/network2\\.conflist
# test for empty mask
@@ -22,7 +22,8 @@ t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[0,255,255,0]}'
# network list
t GET libpod/networks/json 200
-t GET libpod/networks/json?filter=name=network1 200 \
+# filters={"name":["network1"]}
+t GET libpod/networks/json?filters=%7B%22name%22%3A%5B%22network1%22%5D%7D 200 \
length=1 \
.[0].Name=network1
t GET networks 200
@@ -34,12 +35,12 @@ length=2
#filters={"name":["network"]}
t GET networks?filters=%7B%22name%22%3A%5B%22network%22%5D%7D 200 \
length=2
-# invalid filter filters={"label":"abc"}
-t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 500 \
-.cause="only the name filter for listing networks is implemented"
-# invalid filter filters={"label":"abc","name":["network"]}
-t GET networks?filters=%7B%22label%22%3A%22abc%22%2C%22name%22%3A%5B%22network%22%5D%7D 500 \
-.cause="only the name filter for listing networks is implemented"
+# filters={"label":["abc"]}
+t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 200 \
+length=1
+# invalid filter filters={"id":["abc"]}
+t GET networks?filters=%7B%22id%22%3A%5B%22abc%22%5D%7D 500 \
+.cause='invalid filter "id"'
# clean the network
t DELETE libpod/networks/network1 200 \
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index 760345a67..aaf089d97 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "runtime"
"strings"
. "github.com/containers/podman/v2/test/utils"
@@ -644,4 +645,35 @@ var _ = Describe("Podman create", func() {
Expect(session.ErrorToString()).To(ContainSubstring("unknown flag"))
})
+ It("podman create --platform", func() {
+ session := podmanTest.Podman([]string{"create", "--platform=linux/bogus", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError := "no image found in manifest list for architecture bogus"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"create", "--platform=linux/arm64", "--override-os", "windows", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError = "--platform option can not be specified with --overide-arch or --override-os"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"create", "-q", "--platform=linux/arm64", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ setup := podmanTest.Podman([]string{"container", "inspect", session.OutputToString()})
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ data := setup.InspectContainerToJSON()
+ setup = podmanTest.Podman([]string{"image", "inspect", data[0].Image})
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ idata := setup.InspectImageJSON() // returns []inspect.ImageData
+ Expect(len(idata)).To(Equal(1))
+ Expect(idata[0].Os).To(Equal(runtime.GOOS))
+ Expect(idata[0].Architecture).To(Equal("arm64"))
+ })
})
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index 20e1d5b6b..ad6af61c7 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -66,6 +66,65 @@ var _ = Describe("Podman network", func() {
Expect(session.LineInOutputContains(name)).To(BeTrue())
})
+ It("podman network list --filter plugin and name", func() {
+ name, path := generateNetworkConfig(podmanTest)
+ defer removeConf(path)
+
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "plugin=bridge", "--filter", "name=" + name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(name))
+ })
+
+ It("podman network list --filter two names", func() {
+ name1, path1 := generateNetworkConfig(podmanTest)
+ defer removeConf(path1)
+
+ name2, path2 := generateNetworkConfig(podmanTest)
+ defer removeConf(path2)
+
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "name=" + name1, "--filter", "name=" + name2})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(name1))
+ Expect(session.OutputToString()).To(ContainSubstring(name2))
+ })
+
+ It("podman network list --filter labels", func() {
+ net1 := "labelnet" + stringid.GenerateNonCryptoID()
+ label1 := "testlabel1=abc"
+ label2 := "abcdef"
+ session := podmanTest.Podman([]string{"network", "create", "--label", label1, net1})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeCNINetwork(net1)
+ Expect(session.ExitCode()).To(BeZero())
+
+ net2 := "labelnet" + stringid.GenerateNonCryptoID()
+ session = podmanTest.Podman([]string{"network", "create", "--label", label1, "--label", label2, net2})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeCNINetwork(net2)
+ Expect(session.ExitCode()).To(BeZero())
+
+ session = podmanTest.Podman([]string{"network", "ls", "--filter", "label=" + label1})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(net1))
+ Expect(session.OutputToString()).To(ContainSubstring(net2))
+
+ session = podmanTest.Podman([]string{"network", "ls", "--filter", "label=" + label1, "--filter", "label=" + label2})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).ToNot(ContainSubstring(net1))
+ Expect(session.OutputToString()).To(ContainSubstring(net2))
+ })
+
+ It("podman network list --filter invalid value", func() {
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "namr=ab"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(ExitWithError())
+ Expect(session.ErrorToString()).To(ContainSubstring(`invalid filter "namr"`))
+ })
+
It("podman network list --filter failure", func() {
name, path := generateNetworkConfig(podmanTest)
defer removeConf(path)
diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go
index f1b055d6d..c572fc5ac 100644
--- a/test/e2e/pull_test.go
+++ b/test/e2e/pull_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "runtime"
"strings"
. "github.com/containers/podman/v2/test/utils"
@@ -494,4 +495,31 @@ var _ = Describe("Podman pull", func() {
Expect(data[0].ID).To(Equal(image1))
}
})
+
+ It("podman pull --platform", func() {
+ session := podmanTest.Podman([]string{"pull", "--platform=linux/bogus", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError := "no image found in manifest list for architecture bogus"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"pull", "--platform=linux/arm64", "--override-os", "windows", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError = "--platform option can not be specified with --overide-arch or --override-os"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"pull", "-q", "--platform=linux/arm64", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ setup := podmanTest.Podman([]string{"image", "inspect", session.OutputToString()})
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ data := setup.InspectImageJSON() // returns []inspect.ImageData
+ Expect(len(data)).To(Equal(1))
+ Expect(data[0].Os).To(Equal(runtime.GOOS))
+ Expect(data[0].Architecture).To(Equal("arm64"))
+ })
})
diff --git a/test/system/010-images.bats b/test/system/010-images.bats
index 98bb0cc57..ee6da30ec 100644
--- a/test/system/010-images.bats
+++ b/test/system/010-images.bats
@@ -59,7 +59,8 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
@test "podman images - history output" {
# podman history is persistent: it permanently alters our base image.
# Create a dummy image here so we leave our setup as we found it.
- run_podman run --name my-container $IMAGE true
+ # Multiple --name options confirm command-line override (last one wins)
+ run_podman run --name ignore-me --name my-container $IMAGE true
run_podman commit my-container my-test-image
run_podman images my-test-image --format '{{ .History }}'
@@ -87,7 +88,8 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
}
@test "podman images - filter" {
- run_podman inspect --format '{{.ID}}' $IMAGE
+ # Multiple --format options confirm command-line override (last one wins)
+ run_podman inspect --format '{{.XYZ}}' --format '{{.ID}}' $IMAGE
iid=$output
run_podman images --noheading --filter=after=$iid
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index 71831da10..37695f205 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -449,7 +449,9 @@ json-file | f
msg=$(random_string 20)
pidfile="${PODMAN_TMPDIR}/$(random_string 20)"
- run_podman run --name myctr --log-driver journald --conmon-pidfile $pidfile $IMAGE echo $msg
+ # Multiple --log-driver options to confirm that last one wins
+ run_podman run --name myctr --log-driver=none --log-driver journald \
+ --conmon-pidfile $pidfile $IMAGE echo $msg
journalctl --output cat _PID=$(cat $pidfile)
is "$output" "$msg" "check that journalctl output equals the container output"
@@ -464,7 +466,9 @@ json-file | f
run_podman run --rm $IMAGE date -r $testfile
is "$output" "Sun Sep 13 12:26:40 UTC 2020" "podman run with no TZ"
- run_podman run --rm --tz=MST7MDT $IMAGE date -r $testfile
+ # Multiple --tz options; confirm that the last one wins
+ run_podman run --rm --tz=US/Eastern --tz=Iceland --tz=MST7MDT \
+ $IMAGE date -r $testfile
is "$output" "Sun Sep 13 06:26:40 MDT 2020" "podman run with --tz=MST7MDT"
# --tz=local pays attention to /etc/localtime, not $TZ. We set TZ anyway,
@@ -533,8 +537,15 @@ json-file | f
}
@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"
+ rand=$(random_string 10)
+
+ # Please keep the duplicate "--net" options; this tests against #8507,
+ # a regression in which subsequent --net options did not override earlier.
+ run_podman run --rm -p 8080 --net=none --net=host $IMAGE echo $rand
+ is "${lines[0]}" \
+ "Port mappings have been discarded as one of the Host, Container, Pod, and None network modes are in use" \
+ "Warning is emitted before container output"
+ is "${lines[1]}" "$rand" "Container runs successfully despite warning"
}
# vim: filetype=sh
diff --git a/test/system/040-ps.bats b/test/system/040-ps.bats
index dec2df4d5..1ed2779b2 100644
--- a/test/system/040-ps.bats
+++ b/test/system/040-ps.bats
@@ -35,4 +35,51 @@ load helpers
run_podman rm $cid
}
+@test "podman ps --filter" {
+ run_podman run -d --name runner $IMAGE top
+ cid_runner=$output
+
+ run_podman run -d --name stopped $IMAGE true
+ cid_stopped=$output
+ run_podman wait stopped
+
+ run_podman run -d --name failed $IMAGE false
+ cid_failed=$output
+ run_podman wait failed
+
+ run_podman create --name created $IMAGE echo hi
+ cid_created=$output
+
+ run_podman ps --filter name=runner --format '{{.ID}}'
+ is "$output" "${cid_runner:0:12}" "filter: name=runner"
+
+ # Stopped container should not appear (because we're not using -a)
+ run_podman ps --filter name=stopped --format '{{.ID}}'
+ is "$output" "" "filter: name=stopped (without -a)"
+
+ # Again, but with -a
+ run_podman ps -a --filter name=stopped --format '{{.ID}}'
+ is "$output" "${cid_stopped:0:12}" "filter: name=stopped (with -a)"
+
+ run_podman ps --filter status=stopped --format '{{.Names}}' --sort names
+ is "${lines[0]}" "failed" "status=stopped: 1 of 2"
+ is "${lines[1]}" "stopped" "status=stopped: 2 of 2"
+
+ run_podman ps --filter status=exited --filter exited=0 --format '{{.Names}}'
+ is "$output" "stopped" "exited=0"
+
+ run_podman ps --filter status=exited --filter exited=1 --format '{{.Names}}'
+ is "$output" "failed" "exited=1"
+
+ # Multiple statuses allowed; and test sort=created
+ run_podman ps -a --filter status=exited --filter status=running \
+ --format '{{.Names}}' --sort created
+ is "${lines[0]}" "runner" "status=stopped: 1 of 3"
+ is "${lines[1]}" "stopped" "status=stopped: 2 of 3"
+ is "${lines[2]}" "failed" "status=stopped: 3 of 3"
+
+ run_podman stop -t 1 runner
+ run_podman rm -a
+}
+
# vim: filetype=sh
diff --git a/test/system/070-build.bats b/test/system/070-build.bats
index 83bcd13eb..59da503a6 100644
--- a/test/system/070-build.bats
+++ b/test/system/070-build.bats
@@ -135,10 +135,13 @@ echo "\$1"
printenv | grep MYENV | sort | sed -e 's/^MYENV.=//'
EOF
- # For overriding with --env-file
- cat >$PODMAN_TMPDIR/env-file <<EOF
+ # For overriding with --env-file; using multiple files confirms that
+ # the --env-file option is cumulative, not last-one-wins.
+ cat >$PODMAN_TMPDIR/env-file1 <<EOF
MYENV3=$s_env3
http_proxy=http-proxy-in-env-file
+EOF
+ cat >$PODMAN_TMPDIR/env-file2 <<EOF
https_proxy=https-proxy-in-env-file
EOF
@@ -185,7 +188,8 @@ EOF
export MYENV2="$s_env2"
export MYENV3="env-file-should-override-env-host!"
run_podman run --rm \
- --env-file=$PODMAN_TMPDIR/env-file \
+ --env-file=$PODMAN_TMPDIR/env-file1 \
+ --env-file=$PODMAN_TMPDIR/env-file2 \
${ENVHOST} \
-e MYENV4="$s_env4" \
build_test
@@ -205,7 +209,9 @@ EOF
# Proxies - environment should override container, but not env-file
http_proxy=http-proxy-from-env ftp_proxy=ftp-proxy-from-env \
- run_podman run --rm --env-file=$PODMAN_TMPDIR/env-file \
+ run_podman run --rm \
+ --env-file=$PODMAN_TMPDIR/env-file1 \
+ --env-file=$PODMAN_TMPDIR/env-file2 \
build_test \
printenv http_proxy https_proxy ftp_proxy
is "${lines[0]}" "http-proxy-in-env-file" "env-file overrides env"
@@ -222,7 +228,8 @@ EOF
is "$output" "$workdir" "pwd command in container"
# Determine buildah version, so we can confirm it gets into Labels
- run_podman info --format '{{ .Host.BuildahVersion }}'
+ # Multiple --format options confirm command-line override (last one wins)
+ run_podman info --format '{{.Ignore}}' --format '{{ .Host.BuildahVersion }}'
is "$output" "[1-9][0-9.-]\+" ".Host.BuildahVersion is reasonable"
buildah_version=$output
diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats
index edd7dedc4..c028e16c9 100644
--- a/test/system/075-exec.bats
+++ b/test/system/075-exec.bats
@@ -91,7 +91,8 @@ load helpers
# #6829 : add username to /etc/passwd inside container if --userns=keep-id
@test "podman exec - with keep-id" {
- run_podman run -d --userns=keep-id $IMAGE sh -c \
+ # Multiple --userns options confirm command-line override (last one wins)
+ run_podman run -d --userns=private --userns=keep-id $IMAGE sh -c \
"echo READY;while [ ! -f /tmp/stop ]; do sleep 1; done"
cid="$output"
wait_for_ready $cid
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
index b0f645c53..51835e4a3 100644
--- a/test/system/200-pod.bats
+++ b/test/system/200-pod.bats
@@ -286,6 +286,10 @@ EOF
is "$output" "nc: bind: Address in use" \
"two containers cannot bind to same port"
+ # make sure we can ping; failure here might mean that capabilities are wrong
+ run_podman run --rm --pod mypod $IMAGE ping -c1 127.0.0.1
+ run_podman run --rm --pod mypod $IMAGE ping -c1 $hostname
+
# While the container is still running, run 'podman ps' (no --format)
# and confirm that the output includes the published port
run_podman ps --filter id=$cid
diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go
index 2ceebc552..846636d75 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=<TAB>)
- # bash focuses on the part after the =, so we need to remove
- # the flag part from $cur
- if [[ "${cur}" == -*=* ]]; then
- flagPrefix="${cur%%%%=*}="
- cur="${cur#*=}"
- fi
-
- __%[1]s_debug "Calling ${requestComp}"
+ __%[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 __<prefix>_go_custom_completion
+ if flag.Annotations == nil {
+ flag.Annotations = map[string][]string{}
+ }
+ flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
+ }
+}
+
+func writeFlags(buf *bytes.Buffer, cmd *Command) {
+ prepareCustomAnnotationsForFlags(cmd)
+ buf.WriteString(` flags=()
+ two_word_flags=()
+ local_nonpersistent_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+`)
+ localNonPersistentFlags := cmd.LocalNonPersistentFlags()
+ cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ if nonCompletableFlag(flag) {
+ return
+ }
+ writeFlag(buf, flag, cmd)
+ if len(flag.Shorthand) > 0 {
+ writeShortFlag(buf, flag, cmd)
+ }
+ // localNonPersistentFlags are used to stop the completion of subcommands when one is set
+ // if TraverseChildren is true we should allow to complete subcommands
+ if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
+ writeLocalNonPersistentFlag(buf, flag)
+ }
+ })
+ cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ if nonCompletableFlag(flag) {
+ return
+ }
+ writeFlag(buf, flag, cmd)
+ if len(flag.Shorthand) > 0 {
+ writeShortFlag(buf, flag, cmd)
+ }
+ })
+
+ buf.WriteString("\n")
+}
+
+func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" must_have_one_flag=()\n")
+ flags := cmd.NonInheritedFlags()
+ flags.VisitAll(func(flag *pflag.Flag) {
+ if nonCompletableFlag(flag) {
+ return
+ }
+ for key := range flag.Annotations {
+ switch key {
+ case BashCompOneRequiredFlag:
+ format := " must_have_one_flag+=(\"--%s"
+ if flag.Value.Type() != "bool" {
+ format += "="
+ }
+ format += "\")\n"
+ buf.WriteString(fmt.Sprintf(format, flag.Name))
+
+ if len(flag.Shorthand) > 0 {
+ buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand))
+ }
+ }
+ }
+ })
+}
+
+func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" must_have_one_noun=()\n")
+ sort.Sort(sort.StringSlice(cmd.ValidArgs))
+ for _, value := range cmd.ValidArgs {
+ // Remove any description that may be included following a tab character.
+ // Descriptions are not supported by bash completion.
+ value = strings.Split(value, "\t")[0]
+ buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
+ }
+ if cmd.ValidArgsFunction != nil {
+ buf.WriteString(" has_completion_function=1\n")
+ }
+}
+
+func writeCmdAliases(buf *bytes.Buffer, cmd *Command) {
+ if len(cmd.Aliases) == 0 {
+ return
+ }
+
+ sort.Sort(sort.StringSlice(cmd.Aliases))
+
+ buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n"))
+ for _, value := range cmd.Aliases {
+ buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value))
+ buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name()))
+ }
+ buf.WriteString(` fi`)
+ buf.WriteString("\n")
+}
+func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" noun_aliases=()\n")
+ sort.Sort(sort.StringSlice(cmd.ArgAliases))
+ for _, value := range cmd.ArgAliases {
+ buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value))
+ }
+}
+
+func gen(buf *bytes.Buffer, cmd *Command) {
+ for _, c := range cmd.Commands() {
+ if !c.IsAvailableCommand() && c != cmd.helpCommand {
+ continue
+ }
+ gen(buf, c)
+ }
+ commandName := cmd.CommandPath()
+ commandName = strings.Replace(commandName, " ", "_", -1)
+ commandName = strings.Replace(commandName, ":", "__", -1)
+
+ if cmd.Root() == cmd {
+ buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
+ } else {
+ buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
+ }
+
+ buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
+ buf.WriteString("\n")
+ buf.WriteString(" command_aliases=()\n")
+ buf.WriteString("\n")
+
+ writeCommands(buf, cmd)
+ writeFlags(buf, cmd)
+ writeRequiredFlag(buf, cmd)
+ writeRequiredNouns(buf, cmd)
+ writeArgAliases(buf, cmd)
+ buf.WriteString("}\n\n")
+}
+
+// GenBashCompletion generates bash completion file and writes to the passed writer.
+func (c *Command) GenBashCompletion(w io.Writer) error {
+ buf := new(bytes.Buffer)
+ writePreamble(buf, c.Name())
+ if len(c.BashCompletionFunction) > 0 {
+ buf.WriteString(c.BashCompletionFunction + "\n")
+ }
+ gen(buf, c)
+ writePostscript(buf, c.Name())
+
+ _, err := buf.WriteTo(w)
+ return err
+}
+
+func nonCompletableFlag(flag *pflag.Flag) bool {
+ return flag.Hidden || len(flag.Deprecated) > 0
+}
+
+// GenBashCompletionFile generates bash completion file.
+func (c *Command) GenBashCompletionFile(filename string) error {
+ outFile, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer outFile.Close()
+
+ return c.GenBashCompletion(outFile)
}
diff --git a/vendor/github.com/spf13/cobra/custom_completions.go b/vendor/github.com/spf13/cobra/custom_completions.go
index e2c68ae1e..f9e88e081 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 :<directive>
@@ -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 "__<program>_custom_func".
- // This could happen if the program defined some functions for legacy flag completion
- // but not the legacy custom_func.
- if strings.Contains(cmd.Root().BashCompletionFunction, "_custom_func") {
- // For legacy args completion, the script already knows what to call
- // so we only need to tell it the commands to set the shell variables needed
- completions = prepareLegacyCustomCompletionVars(cmd, args)
- directive = directive | shellCompDirectiveLegacyCustomComp | shellCompDirectiveLegacyCustomArgsComp
- }
- }
- }
- }
- return completions, directive
-}
-
-// The original bash completion script had some shell variables that are used by legacy bash
-// custom completions. Let's set those variables to allow those legacy custom completions
-// to continue working.
-func prepareLegacyCustomCompletionVars(cmd *Command, args []string) []string {
- var compVarCmds []string
-
- // "last_command" variable
- commandName := cmd.CommandPath()
- commandName = strings.Replace(commandName, " ", "_", -1)
- commandName = strings.Replace(commandName, ":", "__", -1)
- compVarCmds = append(compVarCmds, fmt.Sprintf("last_command=%s", commandName))
-
- // "nouns" array variable
- compVarCmds = append(compVarCmds, fmt.Sprintf("nouns=(%s)", strings.Join(args, " ")))
-
- return compVarCmds
-}
-
// CompDebug prints the specified string to the same file as where the
// completion script prints its logs.
// Note that completion printouts should never be on stdout as they would
diff --git a/vendor/github.com/spf13/cobra/fish_completions.go b/vendor/github.com/spf13/cobra/fish_completions.go
index aeaef0087..eaae9bca8 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 ef290e2eb..92a70394a 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 857e84cae..9de30e604 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -517,7 +517,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