summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml18
-rw-r--r--cmd/podman/common/completion.go58
-rw-r--r--cmd/podman/common/netflags.go2
-rw-r--r--cmd/podman/containers/logs.go3
-rw-r--r--cmd/podman/images/sign.go1
-rw-r--r--cmd/podman/play/kube.go2
-rw-r--r--cmd/podman/system/connection/add.go61
-rw-r--r--cmd/podman/system/prune.go3
-rw-r--r--cmd/podman/volumes/prune.go16
-rwxr-xr-xcontrib/cirrus/runner.sh4
-rwxr-xr-xcontrib/cirrus/setup_environment.sh1
-rw-r--r--docs/source/markdown/podman-container-prune.1.md12
-rw-r--r--docs/source/markdown/podman-create.1.md4
-rw-r--r--docs/source/markdown/podman-image-prune.1.md12
-rw-r--r--docs/source/markdown/podman-image-sign.1.md4
-rw-r--r--docs/source/markdown/podman-run.1.md4
-rw-r--r--docs/source/markdown/podman-system-prune.1.md16
-rw-r--r--docs/source/markdown/podman-volume-prune.1.md16
-rw-r--r--libpod/logs/log.go16
-rw-r--r--libpod/runtime_volume.go4
-rw-r--r--pkg/api/handlers/compat/containers_create.go15
-rw-r--r--pkg/api/handlers/compat/networks.go6
-rw-r--r--pkg/api/handlers/compat/volumes.go12
-rw-r--r--pkg/api/handlers/libpod/images.go2
-rw-r--r--pkg/api/handlers/libpod/volumes.go20
-rw-r--r--pkg/bindings/connection.go49
-rw-r--r--pkg/bindings/generator/generator.go234
-rw-r--r--pkg/bindings/images/removeoptions_types.go93
-rw-r--r--pkg/bindings/images/rm.go10
-rw-r--r--pkg/bindings/images/types.go8
-rw-r--r--pkg/bindings/test/images_test.go17
-rw-r--r--pkg/bindings/test/manifests_test.go2
-rw-r--r--pkg/bindings/test/volumes_test.go41
-rw-r--r--pkg/bindings/volumes/volumes.go12
-rw-r--r--pkg/domain/entities/container_ps.go2
-rw-r--r--pkg/domain/entities/containers.go6
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/entities/images.go1
-rw-r--r--pkg/domain/entities/system.go1
-rw-r--r--pkg/domain/entities/volumes.go7
-rw-r--r--pkg/domain/filters/helpers.go20
-rw-r--r--pkg/domain/filters/volumes.go3
-rw-r--r--pkg/domain/infra/abi/containers.go4
-rw-r--r--pkg/domain/infra/abi/images.go71
-rw-r--r--pkg/domain/infra/abi/system.go16
-rw-r--r--pkg/domain/infra/abi/volumes.go12
-rw-r--r--pkg/domain/infra/runtime_tunnel.go24
-rw-r--r--pkg/domain/infra/tunnel/containers.go53
-rw-r--r--pkg/domain/infra/tunnel/volumes.go4
-rw-r--r--pkg/ps/ps.go37
-rw-r--r--pkg/terminal/util.go9
-rw-r--r--test/apiv2/10-images.at2
-rw-r--r--test/apiv2/20-containers.at11
-rw-r--r--test/apiv2/30-volumes.at30
-rw-r--r--test/apiv2/35-networks.at8
-rw-r--r--test/compose/README.md47
-rw-r--r--test/compose/env_and_volume/README.md12
-rw-r--r--test/compose/env_and_volume/docker-compose.yml18
-rw-r--r--test/compose/env_and_volume/read/Dockerfile5
-rw-r--r--test/compose/env_and_volume/read/app.py10
-rw-r--r--test/compose/env_and_volume/tests.sh4
-rw-r--r--test/compose/env_and_volume/write/Dockerfile5
-rw-r--r--test/compose/env_and_volume/write/app.py13
-rw-r--r--test/compose/images/README.md5
-rw-r--r--test/compose/images/podman-python/Containerfile3
-rw-r--r--test/compose/mount_and_label/README.md9
-rw-r--r--test/compose/mount_and_label/docker-compose.yml10
-rw-r--r--test/compose/mount_and_label/frontend/Dockerfile5
-rw-r--r--test/compose/mount_and_label/frontend/app.py10
-rw-r--r--test/compose/mount_and_label/setup.sh2
-rw-r--r--test/compose/mount_and_label/teardown.sh1
-rw-r--r--test/compose/mount_and_label/tests.sh4
-rw-r--r--test/compose/port_map_diff_port/README.md9
-rw-r--r--test/compose/port_map_diff_port/docker-compose.yml6
-rw-r--r--test/compose/port_map_diff_port/frontend/Dockerfile5
-rw-r--r--test/compose/port_map_diff_port/frontend/app.py9
-rw-r--r--test/compose/port_map_diff_port/tests.sh3
-rw-r--r--test/compose/setup.sh.example3
-rw-r--r--test/compose/simple_port_map/README.md9
-rw-r--r--test/compose/simple_port_map/docker-compose.yml6
-rw-r--r--test/compose/simple_port_map/frontend/Dockerfile6
-rw-r--r--test/compose/simple_port_map/frontend/app.py10
-rw-r--r--test/compose/simple_port_map/tests.sh3
-rw-r--r--test/compose/teardown.sh.example4
-rwxr-xr-xtest/compose/test-compose345
-rw-r--r--test/e2e/image_sign_test.go16
-rw-r--r--test/e2e/logs_test.go17
-rw-r--r--test/e2e/play_kube_test.go4
-rw-r--r--test/e2e/start_test.go23
-rw-r--r--test/e2e/toolbox_test.go2
-rw-r--r--test/e2e/volume_prune_test.go60
91 files changed, 1650 insertions, 154 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 0fa51be63..8507aa3d2 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -30,7 +30,7 @@ env:
PRIOR_UBUNTU_NAME: "ubuntu-19"
# Google-cloud VM Images
- IMAGE_SUFFIX: "c5402398833246208"
+ IMAGE_SUFFIX: "c4704091098054656"
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}"
UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}"
@@ -434,6 +434,21 @@ apiv2_test_task:
podman_system_info_script: '$SCRIPT_BASE/logcollector.sh podman'
time_script: '$SCRIPT_BASE/logcollector.sh time'
+compose_test_task:
+ name: "compose test on $DISTRO_NV"
+ alias: compose_test
+ depends_on:
+ - validate
+ gce_instance: *standardvm
+ env:
+ <<: *stdenvars
+ TEST_FLAVOR: compose
+ clone_script: *noop # Comes from cache
+ gopath_cache: *ro_gopath_cache
+ setup_script: *setup
+ main_script: *main
+ always: *logs_artifacts
+
# Execute the podman integration tests on all primary platforms and release
# versions, as root, without involving the podman-remote client.
@@ -619,6 +634,7 @@ success_task:
- docker-py_test
- unit_test
- apiv2_test
+ - compose_test
- local_integration_test
- remote_integration_test
- rootless_integration_test
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go
index f792b2713..83fe0723c 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -313,6 +313,10 @@ func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra
return suggestions, directive
}
+func getBoolCompletion(_ string) ([]string, cobra.ShellCompDirective) {
+ return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp
+}
+
/* Autocomplete Functions for cobra ValidArgsFunction */
// AutocompleteContainers - Autocomplete all container names.
@@ -797,6 +801,39 @@ func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string
return volumes, directive
}
+// AutocompleteNetworkFlag - Autocomplete network flag options.
+func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ kv := keyValueCompletion{
+ "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
+ "ns:": func(_ string) ([]string, cobra.ShellCompDirective) {
+ return nil, cobra.ShellCompDirectiveDefault
+ },
+ "bridge": nil,
+ "none": nil,
+ "host": nil,
+ "private": nil,
+ "slirp4netns:": func(s string) ([]string, cobra.ShellCompDirective) {
+ skv := keyValueCompletion{
+ "allow_host_loopback=": getBoolCompletion,
+ "cidr=": nil,
+ "enable_ipv6=": getBoolCompletion,
+ "outbound_addr=": nil,
+ "outbound_addr6=": nil,
+ "port_handler=": func(_ string) ([]string, cobra.ShellCompDirective) {
+ return []string{"rootlesskit", "slirp4netns"}, cobra.ShellCompDirectiveNoFileComp
+ },
+ }
+ return completeKeyValues(s, skv)
+ },
+ }
+
+ networks, _ := getNetworks(cmd, toComplete)
+ suggestions, dir := completeKeyValues(toComplete, kv)
+ // add slirp4netns here it does not work correct if we add it to the kv map
+ suggestions = append(suggestions, "slirp4netns")
+ return append(networks, suggestions...), dir
+}
+
// AutocompleteJSONFormat - Autocomplete format flag option.
// -> "json"
func AutocompleteJSONFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -974,17 +1011,14 @@ func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete stri
// AutocompleteImageFilters - Autocomplete image ls --filter options.
func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- getBool := func(_ string) ([]string, cobra.ShellCompDirective) {
- return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp
- }
getImg := func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) }
kv := keyValueCompletion{
"before=": getImg,
"since=": getImg,
"label=": nil,
"reference=": nil,
- "dangling=": getBool,
- "readonly=": getBool,
+ "dangling=": getBoolCompletion,
+ "readonly=": getBoolCompletion,
}
return completeKeyValues(toComplete, kv)
}
@@ -1004,14 +1038,12 @@ func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete str
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
}
kv := keyValueCompletion{
- "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
- "driver=": local,
- "scope=": local,
- "label=": nil,
- "opt=": nil,
- "dangling=": func(_ string) ([]string, cobra.ShellCompDirective) {
- return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp
- },
+ "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
+ "driver=": local,
+ "scope=": local,
+ "label=": nil,
+ "opt=": nil,
+ "dangling=": getBoolCompletion,
}
return completeKeyValues(toComplete, kv)
}
diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go
index cae52ccaa..9cb4ed550 100644
--- a/cmd/podman/common/netflags.go
+++ b/cmd/podman/common/netflags.go
@@ -63,7 +63,7 @@ func DefineNetFlags(cmd *cobra.Command) {
networkFlagName, containerConfig.NetNS(),
"Connect a container to a network",
)
- _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworks)
+ _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworkFlag)
networkAliasFlagName := "network-alias"
netFlags.StringSlice(
diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go
index d4ede370a..9b562afd8 100644
--- a/cmd/podman/containers/logs.go
+++ b/cmd/podman/containers/logs.go
@@ -122,6 +122,7 @@ func logs(_ *cobra.Command, args []string) error {
}
logsOptions.Since = since
}
- logsOptions.Writer = os.Stdout
+ logsOptions.StdoutWriter = os.Stdout
+ logsOptions.StderrWriter = os.Stderr
return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions)
}
diff --git a/cmd/podman/images/sign.go b/cmd/podman/images/sign.go
index 342536f7c..859d51d51 100644
--- a/cmd/podman/images/sign.go
+++ b/cmd/podman/images/sign.go
@@ -47,6 +47,7 @@ func init() {
certDirFlagName := "cert-dir"
flags.StringVar(&signOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys")
_ = signCommand.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault)
+ flags.BoolVarP(&signOptions.All, "all", "a", false, "Sign all the manifests of the multi-architecture image")
}
func sign(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go
index 5e227d05a..db7280b1d 100644
--- a/cmd/podman/play/kube.go
+++ b/cmd/podman/play/kube.go
@@ -61,7 +61,7 @@ func init() {
networkFlagName := "network"
flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)")
- _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworks)
+ _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag)
logDriverFlagName := "log-driver"
flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container")
diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go
index 57e747451..da5f652c8 100644
--- a/cmd/podman/system/connection/add.go
+++ b/cmd/podman/system/connection/add.go
@@ -168,19 +168,17 @@ func getUserInfo(uri *url.URL) (*url.Userinfo, error) {
}
func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) {
- var authMethods []ssh.AuthMethod
- passwd, set := uri.User.Password()
- if set {
- authMethods = append(authMethods, ssh.Password(passwd))
- }
+ var signers []ssh.Signer
+ passwd, passwdSet := uri.User.Password()
if cmd.Flags().Changed("identity") {
value := cmd.Flag("identity").Value.String()
- auth, err := terminal.PublicKey(value, []byte(passwd))
+ s, err := terminal.PublicKey(value, []byte(passwd))
if err != nil {
return "", errors.Wrapf(err, "failed to read identity %q", value)
}
- authMethods = append(authMethods, auth)
+ signers = append(signers, s)
+ logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
}
if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found {
@@ -190,16 +188,51 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) {
if err != nil {
return "", err
}
- a := agent.NewClient(c)
- authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers))
- }
-
- if len(authMethods) == 0 {
- pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username()))
+ agentSigners, err := agent.NewClient(c).Signers()
if err != nil {
return "", err
}
- authMethods = append(authMethods, ssh.Password(string(pass)))
+
+ signers = append(signers, agentSigners...)
+
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ for _, s := range agentSigners {
+ logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ }
+ }
+
+ var authMethods []ssh.AuthMethod
+ if len(signers) > 0 {
+ var dedup = make(map[string]ssh.Signer)
+ // Dedup signers based on fingerprint, ssh-agent keys override CONTAINER_SSHKEY
+ for _, s := range signers {
+ fp := ssh.FingerprintSHA256(s.PublicKey())
+ if _, found := dedup[fp]; found {
+ logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ dedup[fp] = s
+ }
+
+ var uniq []ssh.Signer
+ for _, s := range dedup {
+ uniq = append(uniq, s)
+ }
+
+ authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
+ return uniq, nil
+ }))
+ }
+
+ if passwdSet {
+ authMethods = append(authMethods, ssh.Password(passwd))
+ }
+
+ if len(authMethods) == 0 {
+ authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) {
+ pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username()))
+ return string(pass), err
+ }))
}
cfg := &ssh.ClientConfig{
diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go
index f57689584..5ee017581 100644
--- a/cmd/podman/system/prune.go
+++ b/cmd/podman/system/prune.go
@@ -46,6 +46,9 @@ func init() {
flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false")
flags.BoolVarP(&pruneOptions.All, "all", "a", false, "Remove all unused data")
flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes")
+ filterFlagName := "filter"
+ flags.StringArrayVar(&pruneOptions.Filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')")
+ _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone)
}
diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go
index d1370120b..0f3ba9ef6 100644
--- a/cmd/podman/volumes/prune.go
+++ b/cmd/podman/volumes/prune.go
@@ -8,10 +8,12 @@ import (
"strings"
"github.com/containers/common/pkg/completion"
+ "github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/domain/filters"
"github.com/spf13/cobra"
)
@@ -28,6 +30,7 @@ var (
RunE: prune,
ValidArgsFunction: completion.AutocompleteNone,
}
+ filter = []string{}
)
func init() {
@@ -37,10 +40,17 @@ func init() {
Parent: volumeCmd,
})
flags := pruneCommand.Flags()
+
+ filterFlagName := "filter"
+ flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')")
+ _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters)
flags.BoolP("force", "f", false, "Do not prompt for confirmation")
}
func prune(cmd *cobra.Command, args []string) error {
+ var (
+ pruneOptions = entities.VolumePruneOptions{}
+ )
// Prompt for confirmation if --force is not set
force, err := cmd.Flags().GetBool("force")
if err != nil {
@@ -58,7 +68,11 @@ func prune(cmd *cobra.Command, args []string) error {
return nil
}
}
- responses, err := registry.ContainerEngine().VolumePrune(context.Background())
+ pruneOptions.Filters, err = filters.ParseFilterArgumentsIntoFilters(filter)
+ if err != nil {
+ return err
+ }
+ responses, err := registry.ContainerEngine().VolumePrune(context.Background(), pruneOptions)
if err != nil {
return err
}
diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh
index fa921f3e4..cc6d155f9 100755
--- a/contrib/cirrus/runner.sh
+++ b/contrib/cirrus/runner.sh
@@ -73,6 +73,10 @@ function _run_apiv2() {
make localapiv2 |& logformatter
}
+function _run_compose() {
+ ./test/compose/test-compose |& logformatter
+}
+
function _run_int() {
dotest integration
}
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index c32b45a4f..a3c0f9a13 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -195,6 +195,7 @@ case "$TEST_FLAVOR" in
build) make clean ;;
unit) ;;
apiv2) ;& # use next item
+ compose) ;&
int) ;&
sys) ;&
bindings) ;&
diff --git a/docs/source/markdown/podman-container-prune.1.md b/docs/source/markdown/podman-container-prune.1.md
index 4b4effb0b..b199f9ebb 100644
--- a/docs/source/markdown/podman-container-prune.1.md
+++ b/docs/source/markdown/podman-container-prune.1.md
@@ -15,6 +15,17 @@ podman-container-prune - Remove all stopped containers from local storage
Provide filter values.
+The --filter flag format is of “key=value”. If there is more than one filter, then pass multiple flags (e.g., --filter "foo=bar" --filter "bif=baz")
+
+Supported filters:
+
+- `until` (_timestamp_) - only remove containers and images created before given timestamp
+- `label` (label=_key_, label=_key=value_, label!=_key_, or label!=_key=value_) - only remove containers and images, with (or without, in case label!=... is used) the specified labels.
+
+The until filter can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time.
+
+The label filter accepts two formats. One is the label=... (label=_key_ or label=_key=value_), which removes containers with the specified labels. The other format is the label!=... (label!=_key_ or label!=_key=value_), which removes containers without the specified labels.
+
#### **--force**, **-f**
Do not provide an interactive prompt for container removal.
@@ -63,3 +74,4 @@ podman(1), podman-ps
## HISTORY
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)
+December 2020, converted filter information from docs.docker.com documentation by Dan Walsh (dwalsh at redhat dot com)
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index b5f5591a9..8deaa8540 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -803,10 +803,6 @@ To generate systemd unit files, please see *podman generate systemd*
Automatically remove the container when it exits. The default is *false*.
-Note that the container will not be removed when it could not be created or
-started successfully. This allows the user to inspect the container after
-failure.
-
#### **--rootfs**
If specified, the first argument refers to an exploded container on the file system.
diff --git a/docs/source/markdown/podman-image-prune.1.md b/docs/source/markdown/podman-image-prune.1.md
index d8558d244..73024ffb8 100644
--- a/docs/source/markdown/podman-image-prune.1.md
+++ b/docs/source/markdown/podman-image-prune.1.md
@@ -22,6 +22,17 @@ Remove dangling images and images that have no associated containers.
Provide filter values.
+The --filter flag format is of “key=value”. If there is more than one filter, then pass multiple flags (e.g., --filter "foo=bar" --filter "bif=baz")
+
+Supported filters:
+
+- `until` (_timestamp_) - only remove containers and images created before given timestamp
+- `label` (label=_key_, label=_key=value_, label!=_key_, or label!=_key=value_) - only remove containers and images, with (or without, in case label!=... is used) the specified labels.
+
+The until filter can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time.
+
+The label filter accepts two formats. One is the label=... (label=_key_ or label=_key=value_), which removes images with the specified labels. The other format is the label!=... (label!=_key_ or label!=_key=value_), which removes images without the specified labels.
+
#### **--force**, **-f**
Do not provide an interactive prompt for container removal.
@@ -93,3 +104,4 @@ podman(1), podman-images
## HISTORY
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)
+December 2020, converted filter information from docs.docker.com documentation by Dan Walsh (dwalsh at redhat dot com)
diff --git a/docs/source/markdown/podman-image-sign.1.md b/docs/source/markdown/podman-image-sign.1.md
index 7a924b80b..3e52bde30 100644
--- a/docs/source/markdown/podman-image-sign.1.md
+++ b/docs/source/markdown/podman-image-sign.1.md
@@ -19,6 +19,10 @@ By default, the signature will be written into `/var/lib/containers/sigstore` fo
Print usage statement.
+#### **--all**, **-a**
+
+Sign all the manifests of the multi-architecture image (default false).
+
#### **--cert-dir**=*path*
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 3241cf9f7..cd45e53ef 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -840,10 +840,6 @@ To generate systemd unit files, please see **podman generate systemd**.
Automatically remove the container when it exits. The default is **false**.
-Note that the container will not be removed when it could not be created or
-started successfully. This allows the user to inspect the container after
-failure.
-
#### **--rmi**=*true|false*
After exit of the container, remove the image unless another
diff --git a/docs/source/markdown/podman-system-prune.1.md b/docs/source/markdown/podman-system-prune.1.md
index 431a11267..791503a2c 100644
--- a/docs/source/markdown/podman-system-prune.1.md
+++ b/docs/source/markdown/podman-system-prune.1.md
@@ -18,6 +18,21 @@ By default, volumes are not removed to prevent important data from being deleted
Recursively remove all unused pod, container, image and volume data (Maximum 50 iterations.)
+#### **--filter**=*filters*
+
+Provide filter values.
+
+The --filter flag format is of “key=value”. If there is more than one filter, then pass multiple flags (e.g., --filter "foo=bar" --filter "bif=baz")
+
+Supported filters:
+
+- `until` (_timestamp_) - only remove containers and images created before given timestamp
+- `label` (label=_key_, label=_key=value_, label!=_key_, or label!=_key=value_) - only remove containers and images, with (or without, in case label!=... is used) the specified labels.
+
+The until filter can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time.
+
+The label filter accepts two formats. One is the label=... (label=_key_ or label=_key=value_), which removes containers and images with the specified labels. The other format is the label!=... (label!=_key_ or label!=_key=value_), which removes containers and images without the specified labels.
+
#### **--force**, **-f**
Do not prompt for confirmation
@@ -35,3 +50,4 @@ podman(1), podman-image-prune(1), podman-container-prune(1), podman-pod-prune(1)
## HISTORY
February 2019, Originally compiled by Dan Walsh (dwalsh at redhat dot com)
+December 2020, converted filter information from docs.docker.com documentation by Dan Walsh (dwalsh at redhat dot com)
diff --git a/docs/source/markdown/podman-volume-prune.1.md b/docs/source/markdown/podman-volume-prune.1.md
index b5f1b7e94..9477cb5d5 100644
--- a/docs/source/markdown/podman-volume-prune.1.md
+++ b/docs/source/markdown/podman-volume-prune.1.md
@@ -8,7 +8,8 @@ podman\-volume\-prune - Remove all unused volumes
## DESCRIPTION
-Removes all unused volumes. You will be prompted to confirm the removal of all the
+Removes unused volumes. By default all unused volumes will be removed, the **--filter** flag can
+be used to filter specific volumes. You will be prompted to confirm the removal of all the
unused volumes. To bypass the confirmation, use the **--force** flag.
@@ -18,6 +19,17 @@ unused volumes. To bypass the confirmation, use the **--force** flag.
Do not prompt for confirmation.
+#### **--filter**
+
+Filter volumes to be pruned. Volumes can be filtered by the following attributes:
+
+- dangling
+- driver
+- label
+- name
+- opt
+- scope
+
#### **--help**
Print usage statement
@@ -29,6 +41,8 @@ Print usage statement
$ podman volume prune
$ podman volume prune --force
+
+$ podman volume prune --filter label=mylabel=mylabelvalue
```
## SEE ALSO
diff --git a/libpod/logs/log.go b/libpod/logs/log.go
index a9554088b..d3d83747f 100644
--- a/libpod/logs/log.go
+++ b/libpod/logs/log.go
@@ -210,3 +210,19 @@ func NewLogLine(line string) (*LogLine, error) {
func (l *LogLine) Partial() bool {
return l.ParseLogType == PartialLogType
}
+
+func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) {
+ switch l.Device {
+ case "stdout":
+ if stdout != nil {
+ fmt.Fprintln(stdout, l.String(logOpts))
+ }
+ case "stderr":
+ if stderr != nil {
+ fmt.Fprintln(stderr, l.String(logOpts))
+ }
+ default:
+ // Warn the user if the device type does not match. Most likely the file is corrupted.
+ logrus.Warnf("unknown Device type '%s' in log file from Container %s", l.Device, l.CID)
+ }
+}
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 055a243c0..10c32a119 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -133,9 +133,9 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
}
// PruneVolumes removes unused volumes from the system
-func (r *Runtime) PruneVolumes(ctx context.Context) (map[string]error, error) {
+func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) (map[string]error, error) {
reports := make(map[string]error)
- vols, err := r.GetAllVolumes()
+ vols, err := r.Volumes(filterFuncs...)
if err != nil {
return nil, err
}
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 409a74de2..6e85872b2 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -66,7 +66,20 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()"))
return
}
- sg := specgen.NewSpecGenerator(newImage.ID(), cliOpts.RootFS)
+
+ imgNameOrID := newImage.ID()
+ // if the img had multi names with the same sha256 ID, should use the InputName, not the ID
+ if len(newImage.Names()) > 1 {
+ imageRef, err := utils.ParseDockerReference(newImage.InputName)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
+ return
+ }
+ // maybe the InputName has no tag, so use full name to display
+ imgNameOrID = imageRef.DockerReference().String()
+ }
+
+ sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS)
if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen"))
return
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index fe13971b0..f0b922885 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -131,7 +131,7 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt
Name: conf.Name,
ID: network.GetNetworkID(conf.Name),
Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
- Scope: "",
+ Scope: "local",
Driver: network.DefaultNetworkDriver,
EnableIPv6: false,
IPAM: dockerNetwork.IPAM{
@@ -197,7 +197,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
}
var reports []*types.NetworkResource
- logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
+ logrus.Debugf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames {
report, err := getNetworkResourceByNameOrID(name, runtime, query.Filters)
if err != nil {
@@ -239,7 +239,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
Internal: networkCreate.Internal,
Labels: networkCreate.Labels,
}
- if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
+ if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 {
if len(networkCreate.IPAM.Config) > 1 {
utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config"))
return
diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go
index 71b848932..f76e18ee3 100644
--- a/pkg/api/handlers/compat/volumes.go
+++ b/pkg/api/handlers/compat/volumes.go
@@ -3,6 +3,7 @@ package compat
import (
"encoding/json"
"net/http"
+ "net/url"
"time"
"github.com/containers/podman/v2/libpod"
@@ -254,14 +255,15 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
- // TODO: We have no ability to pass pruning filters to `PruneVolumes()` so
- // we'll explicitly reject the request if we see any
- if len(query.Filters) > 0 {
- utils.InternalServerError(w, errors.New("filters for pruning volumes is not implemented"))
+
+ f := (url.Values)(query.Filters)
+ filterFuncs, err := filters.GenerateVolumeFilters(f)
+ if err != nil {
+ utils.Error(w, "Something when wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse filters for %s", f.Encode()))
return
}
- pruned, err := runtime.PruneVolumes(r.Context())
+ pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs)
if err != nil {
utils.InternalServerError(w, err)
return
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 6145207ca..505c96126 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -51,7 +51,7 @@ func ImageExists(w http.ResponseWriter, r *http.Request) {
return
}
if !report.Value {
- utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(nil, "failed to find image %s", name))
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("failed to find image %s", name))
return
}
utils.WriteResponse(w, http.StatusNoContent, "")
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index b0d40fd8b..b02a6a8ce 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -3,6 +3,7 @@ package libpod
import (
"encoding/json"
"net/http"
+ "net/url"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
@@ -180,8 +181,25 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
func pruneVolumesHelper(r *http.Request) ([]*entities.VolumePruneReport, error) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
)
- pruned, err := runtime.PruneVolumes(r.Context())
+ query := struct {
+ Filters map[string][]string `schema:"filters"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ return nil, err
+ }
+
+ f := (url.Values)(query.Filters)
+ filterFuncs, err := filters.GenerateVolumeFilters(f)
+ if err != nil {
+ return nil, err
+ }
+
+ pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index f2cb3147c..7b26037eb 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -182,30 +182,65 @@ func pingNewConnection(ctx context.Context) error {
func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) {
// if you modify the authmethods or their conditionals, you will also need to make similar
// changes in the client (currently cmd/podman/system/connection/add getUDS).
- authMethods := []ssh.AuthMethod{}
+
+ var signers []ssh.Signer // order Signers are appended to this list determines which key is presented to server
+
if len(identity) > 0 {
- auth, err := terminal.PublicKey(identity, []byte(passPhrase))
+ s, err := terminal.PublicKey(identity, []byte(passPhrase))
if err != nil {
return Connection{}, errors.Wrapf(err, "failed to parse identity %q", identity)
}
- logrus.Debugf("public key signer enabled for identity %q", identity)
- authMethods = append(authMethods, auth)
+
+ signers = append(signers, s)
+ logrus.Debugf("SSH Ident Key %q %s %s", identity, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
}
if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found {
- logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock)
+ logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer(s) enabled", sock)
c, err := net.Dial("unix", sock)
if err != nil {
return Connection{}, err
}
- a := agent.NewClient(c)
- authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers))
+
+ agentSigners, err := agent.NewClient(c).Signers()
+ if err != nil {
+ return Connection{}, err
+ }
+ signers = append(signers, agentSigners...)
+
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ for _, s := range agentSigners {
+ logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ }
+ }
+
+ var authMethods []ssh.AuthMethod
+ if len(signers) > 0 {
+ var dedup = make(map[string]ssh.Signer)
+ // Dedup signers based on fingerprint, ssh-agent keys override CONTAINER_SSHKEY
+ for _, s := range signers {
+ fp := ssh.FingerprintSHA256(s.PublicKey())
+ if _, found := dedup[fp]; found {
+ logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ dedup[fp] = s
+ }
+
+ var uniq []ssh.Signer
+ for _, s := range dedup {
+ uniq = append(uniq, s)
+ }
+ authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
+ return uniq, nil
+ }))
}
if pw, found := _url.User.Password(); found {
authMethods = append(authMethods, ssh.Password(pw))
}
+
if len(authMethods) == 0 {
callback := func() (string, error) {
pass, err := terminal.ReadPassword("Login password:")
diff --git a/pkg/bindings/generator/generator.go b/pkg/bindings/generator/generator.go
new file mode 100644
index 000000000..24c2310ff
--- /dev/null
+++ b/pkg/bindings/generator/generator.go
@@ -0,0 +1,234 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strings"
+ "text/template"
+ "time"
+)
+
+var bodyTmpl = `package {{.PackageName}}
+
+import (
+{{range $import := .Imports}} {{$import}}
+{{end}}
+
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+
+Created {{.Date}}
+*/
+
+// Changed
+func (o *{{.StructName}}) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *{{.StructName}}) ToParams() (url.Values, error) {
+ params := url.Values{}
+ if o == nil {
+ return params, nil
+ }
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ s := reflect.ValueOf(o)
+ if reflect.Ptr == s.Kind() {
+ s = s.Elem()
+ }
+ sType := s.Type()
+ for i := 0; i < s.NumField(); i++ {
+ fieldName := sType.Field(i).Name
+ if !o.Changed(fieldName) {
+ continue
+ }
+ f := s.Field(i)
+ if reflect.Ptr == f.Kind() {
+ f = f.Elem()
+ }
+ switch f.Kind() {
+ case reflect.Bool:
+ params.Set(fieldName, strconv.FormatBool(f.Bool()))
+ case reflect.String:
+ params.Set(fieldName, f.String())
+ case reflect.Int, reflect.Int64:
+ // f.Int() is always an int64
+ params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
+ case reflect.Slice:
+ typ := reflect.TypeOf(f.Interface()).Elem()
+ slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap())
+ switch typ.Kind() {
+ case reflect.String:
+ s, ok := slice.Interface().([]string)
+ if !ok {
+ return nil, errors.New("failed to convert to string slice")
+ }
+ for _, val := range s {
+ params.Add(fieldName, val)
+ }
+ default:
+ return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
+ }
+ case reflect.Map:
+ lowerCaseKeys := make(map[string][]string)
+ // I dont know if this code is needed anymore, TBD
+ // for k, v := range filters {
+ // lowerCaseKeys[strings.ToLower(k)] = v
+ // }
+ s, err := json.MarshalToString(lowerCaseKeys)
+ if err != nil {
+ return nil, err
+ }
+
+ params.Set(fieldName, s)
+ default:
+ return nil, errors.Errorf("unknown type %s", f.Kind().String())
+ }
+ }
+ return params, nil
+}
+`
+
+var fieldTmpl = `
+// With{{.Name}}
+func(o *{{.StructName}}) With{{.Name}}(value {{.Type}}) *{{.StructName}} {
+ v := &value
+ o.{{.Name}} = v
+ return o
+}
+`
+
+type fieldStruct struct {
+ Name string
+ StructName string
+ Type string
+}
+
+func main() {
+ var (
+ closed bool
+ fieldStructs []fieldStruct
+ structNode ast.Node
+ )
+ srcFile := os.Getenv("GOFILE")
+ pkg := os.Getenv("GOPACKAGE")
+ inputStructName := os.Args[1]
+ b, err := ioutil.ReadFile(srcFile)
+ if err != nil {
+ panic(err)
+ }
+ fset := token.NewFileSet() // positions are relative to fset
+ f, err := parser.ParseFile(fset, "", b, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+
+ // always add reflect
+ imports := []string{"\"reflect\""}
+ for _, imp := range f.Imports {
+ imports = append(imports, imp.Path.Value)
+ }
+
+ out, err := os.Create(strings.ToLower(inputStructName) + "_" + srcFile)
+ if err != nil {
+ panic(err)
+ }
+ defer func() {
+ if !closed {
+ out.Close()
+ }
+ }()
+ bodyStruct := struct {
+ PackageName string
+ Imports []string
+ Date string
+ StructName string
+ }{
+ PackageName: pkg,
+ Imports: imports,
+ Date: time.Now().String(),
+ StructName: inputStructName,
+ }
+
+ body := template.Must(template.New("body").Parse(bodyTmpl))
+ fields := template.Must(template.New("fields").Parse(fieldTmpl))
+ ast.Inspect(f, func(n ast.Node) bool {
+ ref, refOK := n.(*ast.TypeSpec)
+ if refOK {
+ if ref.Name.Name == inputStructName {
+ structNode = n
+ x := ref.Type.(*ast.StructType)
+ for _, field := range x.Fields.List {
+ var (
+ name string
+ )
+ typeExpr := field.Type
+ start := typeExpr.Pos() - 1
+ end := typeExpr.End() - 1
+ fieldType := strings.Replace(string(b[start:end]), "*", "", 1)
+ if len(field.Names) > 0 {
+ name = field.Names[0].Name
+ if len(name) < 1 {
+ panic(errors.New("bad name"))
+ }
+ }
+ fStruct := fieldStruct{
+ Name: name,
+ StructName: inputStructName,
+ Type: fieldType,
+ }
+ fieldStructs = append(fieldStructs, fStruct)
+ } // for
+
+ // create the body
+ if err := body.Execute(out, bodyStruct); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ // create with func from the struct fields
+ for _, fs := range fieldStructs {
+ if err := fields.Execute(out, fs); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ // close out file
+ if err := out.Close(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ closed = true
+
+ // go fmt file
+ gofmt := exec.Command("gofmt", "-w", "-s", out.Name())
+ gofmt.Stderr = os.Stdout
+ if err := gofmt.Run(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ // go import file
+ goimport := exec.Command("goimports", "-w", out.Name())
+ goimport.Stderr = os.Stdout
+ if err := goimport.Run(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ }
+ return true
+ })
+}
diff --git a/pkg/bindings/images/removeoptions_types.go b/pkg/bindings/images/removeoptions_types.go
new file mode 100644
index 000000000..5902bf908
--- /dev/null
+++ b/pkg/bindings/images/removeoptions_types.go
@@ -0,0 +1,93 @@
+package images
+
+import (
+ "net/url"
+ "reflect"
+ "strconv"
+
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+
+Created 2020-12-10 12:51:06.090426622 -0600 CST m=+0.000133169
+*/
+
+// Changed
+func (o *RemoveOptions) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *RemoveOptions) ToParams() (url.Values, error) {
+ params := url.Values{}
+ if o == nil {
+ return params, nil
+ }
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ s := reflect.ValueOf(o)
+ if reflect.Ptr == s.Kind() {
+ s = s.Elem()
+ }
+ sType := s.Type()
+ for i := 0; i < s.NumField(); i++ {
+ fieldName := sType.Field(i).Name
+ if !o.Changed(fieldName) {
+ continue
+ }
+ f := s.Field(i)
+ if reflect.Ptr == f.Kind() {
+ f = f.Elem()
+ }
+ switch f.Kind() {
+ case reflect.Bool:
+ params.Set(fieldName, strconv.FormatBool(f.Bool()))
+ case reflect.String:
+ params.Set(fieldName, f.String())
+ case reflect.Int, reflect.Int64:
+ // f.Int() is always an int64
+ params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
+ case reflect.Slice:
+ typ := reflect.TypeOf(f.Interface()).Elem()
+ slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap())
+ switch typ.Kind() {
+ case reflect.String:
+ s, ok := slice.Interface().([]string)
+ if !ok {
+ return nil, errors.New("failed to convert to string slice")
+ }
+ for _, val := range s {
+ params.Add(fieldName, val)
+ }
+ default:
+ return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
+ }
+ case reflect.Map:
+ lowerCaseKeys := make(map[string][]string)
+ // I dont know if this code is needed anymore, TBD
+ // for k, v := range filters {
+ // lowerCaseKeys[strings.ToLower(k)] = v
+ // }
+ s, err := json.MarshalToString(lowerCaseKeys)
+ if err != nil {
+ return nil, err
+ }
+
+ params.Set(fieldName, s)
+ default:
+ return nil, errors.Errorf("unknown type %s", f.Kind().String())
+ }
+ }
+ return params, nil
+}
+
+// WithForce
+func (o *RemoveOptions) WithForce(value bool) *RemoveOptions {
+ v := &value
+ o.Force = v
+ return o
+}
diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go
index 9685b75e4..0b3b88165 100644
--- a/pkg/bindings/images/rm.go
+++ b/pkg/bindings/images/rm.go
@@ -41,17 +41,19 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove
return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors)
}
-// Remove removes an image from the local storage. Use force to remove an
+// Remove removes an image from the local storage. Use optional force option to remove an
// image, even if it's used by containers.
-func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) {
+func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entities.ImageRemoveReport, error) {
var report handlers.LibpodImagesRemoveReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
- params := url.Values{}
- params.Set("force", strconv.FormatBool(force))
+ params, err := options.ToParams()
+ if err != nil {
+ return nil, err
+ }
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID)
if err != nil {
return nil, err
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
new file mode 100644
index 000000000..340c7bdb9
--- /dev/null
+++ b/pkg/bindings/images/types.go
@@ -0,0 +1,8 @@
+package images
+
+//go:generate go run ../generator/generator.go RemoveOptions
+// RemoveOptions are optional options for image removal
+type RemoveOptions struct {
+ // Forces removes all containers based on the image
+ Force *bool
+}
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 7d9415f91..684f110e8 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -84,7 +84,7 @@ var _ = Describe("Podman images", func() {
// Test to validate the remove image api
It("remove image", func() {
// Remove invalid image should be a 404
- response, err := images.Remove(bt.conn, "foobar5000", false)
+ response, err := images.Remove(bt.conn, "foobar5000", nil)
Expect(err).ToNot(BeNil())
Expect(response).To(BeNil())
code, _ := bindings.CheckResponseCode(err)
@@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() {
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
- response, err = images.Remove(bt.conn, busybox.shortName, false)
+ response, err = images.Remove(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
code, _ = bindings.CheckResponseCode(err)
@@ -113,12 +113,13 @@ var _ = Describe("Podman images", func() {
// try to remove the image "alpine". This should fail since we are not force
// deleting hence image cannot be deleted until the container is deleted.
- response, err = images.Remove(bt.conn, alpine.shortName, false)
+ response, err = images.Remove(bt.conn, alpine.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusConflict))
// Removing the image "alpine" where force = true
- response, err = images.Remove(bt.conn, alpine.shortName, true)
+ options := images.RemoveOptions{}
+ response, err = images.Remove(bt.conn, alpine.shortName, options.WithForce(true))
Expect(err).To(BeNil())
// To be extra sure, check if the previously created container
// is gone as well.
@@ -213,7 +214,7 @@ var _ = Describe("Podman images", func() {
It("Load|Import Image", func() {
// load an image
- _, err := images.Remove(bt.conn, alpine.name, false)
+ _, err := images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -231,7 +232,7 @@ var _ = Describe("Podman images", func() {
// load with a repo name
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, false)
+ _, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -247,7 +248,7 @@ var _ = Describe("Podman images", func() {
// load with a bad repo name should trigger a 500
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, false)
+ _, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -275,7 +276,7 @@ var _ = Describe("Podman images", func() {
It("Import Image", func() {
// load an image
- _, err = images.Remove(bt.conn, alpine.name, false)
+ _, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index 55fc4cb0d..a4ecaa20f 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() {
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
- _, err = images.Remove(bt.conn, id, false)
+ _, err = images.Remove(bt.conn, id, nil)
Expect(err).To(BeNil())
// create manifest list with images
diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go
index dc90d4d00..861a02441 100644
--- a/pkg/bindings/test/volumes_test.go
+++ b/pkg/bindings/test/volumes_test.go
@@ -144,16 +144,15 @@ var _ = Describe("Podman volumes", func() {
Expect(vols[0].Name).To(Equal("homer"))
})
- // TODO we need to add filtering to tests
It("prune unused volume", func() {
// Pruning when no volumes present should be ok
- _, err := volumes.Prune(connText)
+ _, err := volumes.Prune(connText, nil)
Expect(err).To(BeNil())
// Removing an unused volume should work
_, err = volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
- vols, err := volumes.Prune(connText)
+ vols, err := volumes.Prune(connText, nil)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 1))
@@ -163,11 +162,45 @@ var _ = Describe("Podman volumes", func() {
Expect(err).To(BeNil())
session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"})
session.Wait(45)
- vols, err = volumes.Prune(connText)
+ vols, err = volumes.Prune(connText, nil)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 1))
_, err = volumes.Inspect(connText, "homer")
Expect(err).To(BeNil())
+
+ // Removing volume with non matching filter shouldn't prune any volumes
+ filters := make(map[string][]string)
+ filters["label"] = []string{"label1=idontmatch"}
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{
+ "label1": "value1",
+ }})
+ Expect(err).To(BeNil())
+ vols, err = volumes.Prune(connText, filters)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 0))
+ vol2, err := volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{
+ "label1": "value2",
+ }})
+ Expect(err).To(BeNil())
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{
+ "label1": "value3",
+ }})
+ Expect(err).To(BeNil())
+
+ // Removing volume with matching filter label and value should remove specific entry
+ filters = make(map[string][]string)
+ filters["label"] = []string{"label1=value2"}
+ vols, err = volumes.Prune(connText, filters)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 1))
+ Expect(vols[0].Id).To(Equal(vol2.Name))
+
+ // Removing volumes with matching filter label should remove all matching volumes
+ filters = make(map[string][]string)
+ filters["label"] = []string{"label1"}
+ vols, err = volumes.Prune(connText, filters)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 2))
})
})
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
index 00f1e5720..b1be257b8 100644
--- a/pkg/bindings/volumes/volumes.go
+++ b/pkg/bindings/volumes/volumes.go
@@ -75,7 +75,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL
}
// Prune removes unused volumes from the local filesystem.
-func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
+func Prune(ctx context.Context, filters map[string][]string) ([]*entities.VolumePruneReport, error) {
var (
pruned []*entities.VolumePruneReport
)
@@ -83,7 +83,15 @@ func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil)
+ params := url.Values{}
+ if len(filters) > 0 {
+ strFilters, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", strFilters)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go
index b4e8446cb..ff3b087ed 100644
--- a/pkg/domain/entities/container_ps.go
+++ b/pkg/domain/entities/container_ps.go
@@ -12,6 +12,8 @@ import (
// Listcontainer describes a container suitable for listing
type ListContainer struct {
+ // AutoRemove
+ AutoRemove bool
// Container command
Command []string
// Container creation time
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 39d679eaf..01086a2b3 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -227,8 +227,10 @@ type ContainerLogsOptions struct {
Tail int64
// Show timestamps in the logs.
Timestamps bool
- // Write the logs to Writer.
- Writer io.Writer
+ // Write the stdout to this Writer.
+ StdoutWriter io.Writer
+ // Write the stderr to this Writer.
+ StderrWriter io.Writer
}
// ExecOptions describes the cli values to exec into
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 5ad475133..ac9073402 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -80,6 +80,6 @@ type ContainerEngine interface {
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error)
VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error)
VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error)
- VolumePrune(ctx context.Context) ([]*VolumePruneReport, error)
+ VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*VolumePruneReport, error)
VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error)
}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 81f12bff7..1538cbb8b 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -344,6 +344,7 @@ type SignOptions struct {
Directory string
SignBy string
CertDir string
+ All bool
}
// SignReport describes the result of signing
diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go
index bde2b6ef2..4af013134 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -19,6 +19,7 @@ type ServiceOptions struct {
type SystemPruneOptions struct {
All bool
Volume bool
+ Filter []string
}
// SystemPruneReport provides report after system prune is executed.
diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go
index 1bc1e4301..e6b29e374 100644
--- a/pkg/domain/entities/volumes.go
+++ b/pkg/domain/entities/volumes.go
@@ -1,6 +1,7 @@
package entities
import (
+ "net/url"
"time"
docker_api_types "github.com/docker/docker/api/types"
@@ -109,6 +110,12 @@ type VolumeInspectReport struct {
*VolumeConfigResponse
}
+// VolumePruneOptions describes the options needed
+// to prune a volume from the CLI
+type VolumePruneOptions struct {
+ Filters url.Values `json:"filters" schema:"filters"`
+}
+
type VolumePruneReport struct {
Err error
Id string //nolint
diff --git a/pkg/domain/filters/helpers.go b/pkg/domain/filters/helpers.go
new file mode 100644
index 000000000..6a5fb68b1
--- /dev/null
+++ b/pkg/domain/filters/helpers.go
@@ -0,0 +1,20 @@
+package filters
+
+import (
+ "net/url"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+func ParseFilterArgumentsIntoFilters(filters []string) (url.Values, error) {
+ parsedFilters := make(url.Values)
+ for _, f := range filters {
+ t := strings.SplitN(f, "=", 2)
+ if len(t) < 2 {
+ return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ parsedFilters.Add(t[0], t[1])
+ }
+ return parsedFilters, nil
+}
diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go
index 7819d3cdf..69bef4961 100644
--- a/pkg/domain/filters/volumes.go
+++ b/pkg/domain/filters/volumes.go
@@ -1,13 +1,14 @@
package filters
import (
+ "net/url"
"strings"
"github.com/containers/podman/v2/libpod"
"github.com/pkg/errors"
)
-func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) {
+func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) {
var vf []libpod.VolumeFilter
for filter, v := range filters {
for _, val := range v {
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index ff4277a2e..ec65dbe44 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -925,7 +925,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
}
func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
- if options.Writer == nil {
+ if options.StdoutWriter == nil && options.StderrWriter == nil {
return errors.New("no io.Writer set for container logs")
}
@@ -963,7 +963,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin
}()
for line := range logChannel {
- fmt.Fprintln(options.Writer, line.String(logOpts))
+ line.Write(options.StdoutWriter, options.StderrWriter, logOpts)
}
return nil
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 57a2bc4cf..394ba359c 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -28,6 +28,8 @@ import (
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
"github.com/containers/storage"
+ dockerRef "github.com/docker/distribution/reference"
+ "github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -718,9 +720,9 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
logrus.Errorf("unable to close %s image source %q", srcRef.DockerReference().Name(), err)
}
}()
- getManifest, _, err := rawSource.GetManifest(ctx, nil)
+ topManifestBlob, manifestType, err := rawSource.GetManifest(ctx, nil)
if err != nil {
- return errors.Wrapf(err, "error getting getManifest")
+ return errors.Wrapf(err, "error getting manifest blob")
}
dockerReference := rawSource.Reference().DockerReference()
if dockerReference == nil {
@@ -743,34 +745,34 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
return err
}
}
- manifestDigest, err := manifest.Digest(getManifest)
+ manifestDigest, err := manifest.Digest(topManifestBlob)
if err != nil {
return err
}
- // create signature
- newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
- if err != nil {
- return errors.Wrapf(err, "error creating new signature")
- }
- // create the signstore file
- signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, manifestDigest.Algorithm(), manifestDigest.Hex())
- if err := os.MkdirAll(signatureDir, 0751); err != nil {
- // The directory is allowed to exist
- if !os.IsExist(err) {
- logrus.Error(err)
- return nil
+ if options.All {
+ if !manifest.MIMETypeIsMultiImage(manifestType) {
+ return errors.Errorf("%s is not a multi-architecture image (manifest type %s)", signimage, manifestType)
+ }
+ list, err := manifest.ListFromBlob(topManifestBlob, manifestType)
+ if err != nil {
+ return errors.Wrapf(err, "Error parsing manifest list %q", string(topManifestBlob))
+ }
+ instanceDigests := list.Instances()
+ for _, instanceDigest := range instanceDigests {
+ digest := instanceDigest
+ man, _, err := rawSource.GetManifest(ctx, &digest)
+ if err != nil {
+ return err
+ }
+ if err = putSignature(man, mech, sigStoreDir, instanceDigest, dockerReference, options); err != nil {
+ return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), instanceDigest)
+ }
}
- }
- sigFilename, err := getSigFilename(signatureDir)
- if err != nil {
- logrus.Errorf("error creating sigstore file: %v", err)
return nil
}
- err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644)
- if err != nil {
- logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
- return nil
+ if err = putSignature(topManifestBlob, mech, sigStoreDir, manifestDigest, dockerReference, options); err != nil {
+ return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), manifestDigest)
}
return nil
}()
@@ -806,3 +808,26 @@ func localPathFromURI(url *url.URL) (string, error) {
}
return url.Path, nil
}
+
+// putSignature creates signature and saves it to the signstore file
+func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error {
+ newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy)
+ if err != nil {
+ return err
+ }
+ signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex())
+ if err := os.MkdirAll(signatureDir, 0751); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ return err
+ }
+ }
+ sigFilename, err := getSigFilename(signatureDir)
+ if err != nil {
+ return err
+ }
+ if err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 7ed58092b..b6da364fc 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io/ioutil"
+ "net/url"
"os"
"os/exec"
"path/filepath"
@@ -179,7 +180,16 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
found = true
}
systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...)
- containerPruneReport, err := ic.pruneContainersHelper(nil)
+ containerPruneOptions := entities.ContainerPruneOptions{}
+ for _, f := range options.Filter {
+ t := strings.SplitN(f, "=", 2)
+ containerPruneOptions.Filters = make(url.Values)
+ if len(t) < 2 {
+ return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ containerPruneOptions.Filters.Add(t[0], t[1])
+ }
+ containerPruneReport, err := ic.ContainerPrune(ctx, containerPruneOptions)
if err != nil {
return nil, err
}
@@ -194,7 +204,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
}
}
- results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, nil)
+ results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, options.Filter)
if err != nil {
return nil, err
@@ -214,7 +224,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
systemPruneReport.ImagePruneReport.Report.Id = append(systemPruneReport.ImagePruneReport.Report.Id, results...)
}
if options.Volume {
- volumePruneReport, err := ic.pruneVolumesHelper(ctx)
+ volumePruneReport, err := ic.pruneVolumesHelper(ctx, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go
index a7262f61b..515e52754 100644
--- a/pkg/domain/infra/abi/volumes.go
+++ b/pkg/domain/infra/abi/volumes.go
@@ -127,12 +127,16 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
return reports, errs, nil
}
-func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
- return ic.pruneVolumesHelper(ctx)
+func (ic *ContainerEngine) VolumePrune(ctx context.Context, options entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
+ filterFuncs, err := filters.GenerateVolumeFilters(options.Filters)
+ if err != nil {
+ return nil, err
+ }
+ return ic.pruneVolumesHelper(ctx, filterFuncs)
}
-func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context) ([]*entities.VolumePruneReport, error) {
- pruned, err := ic.Libpod.PruneVolumes(ctx)
+func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context, filterFuncs []libpod.VolumeFilter) ([]*entities.VolumePruneReport, error) {
+ pruned, err := ic.Libpod.PruneVolumes(ctx, filterFuncs)
if err != nil {
return nil, err
}
diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go
index 6c85e837e..3fddf577c 100644
--- a/pkg/domain/infra/runtime_tunnel.go
+++ b/pkg/domain/infra/runtime_tunnel.go
@@ -5,18 +5,38 @@ package infra
import (
"context"
"fmt"
+ "sync"
"github.com/containers/podman/v2/pkg/bindings"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/tunnel"
)
+var (
+ connectionMutex = &sync.Mutex{}
+ connection *context.Context
+)
+
+func newConnection(uri string, identity string) (context.Context, error) {
+ connectionMutex.Lock()
+ defer connectionMutex.Unlock()
+
+ if connection == nil {
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), uri, identity)
+ if err != nil {
+ return ctx, err
+ }
+ connection = &ctx
+ }
+ return *connection, nil
+}
+
func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
return nil, fmt.Errorf("direct runtime not supported")
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
+ ctx, err := newConnection(facts.URI, facts.Identity)
return &tunnel.ContainerEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
@@ -28,7 +48,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
case entities.ABIMode:
return nil, fmt.Errorf("direct image runtime not supported")
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
+ ctx, err := newConnection(facts.URI, facts.Identity)
return &tunnel.ImageEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index e65fef0a4..ad7688f62 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -360,11 +360,12 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, options entities.ContainerLogsOptions) error {
since := options.Since.Format(time.RFC3339)
tail := strconv.FormatInt(options.Tail, 10)
- stdout := options.Writer != nil
+ stdout := options.StdoutWriter != nil
+ stderr := options.StderrWriter != nil
opts := containers.LogOptions{
Follow: &options.Follow,
Since: &since,
- Stderr: &stdout,
+ Stderr: &stderr,
Stdout: &stdout,
Tail: &tail,
Timestamps: &options.Timestamps,
@@ -372,10 +373,11 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string,
}
var err error
- outCh := make(chan string)
+ stdoutCh := make(chan string)
+ stderrCh := make(chan string)
ctx, cancel := context.WithCancel(context.Background())
go func() {
- err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, outCh, outCh)
+ err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, stdoutCh, stderrCh)
cancel()
}()
@@ -383,8 +385,14 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string,
select {
case <-ctx.Done():
return err
- case line := <-outCh:
- _, _ = io.WriteString(options.Writer, line+"\n")
+ case line := <-stdoutCh:
+ if options.StdoutWriter != nil {
+ _, _ = io.WriteString(options.StdoutWriter, line+"\n")
+ }
+ case line := <-stderrCh:
+ if options.StderrWriter != nil {
+ _, _ = io.WriteString(options.StderrWriter, line+"\n")
+ }
}
}
}
@@ -515,6 +523,29 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
reports = append(reports, &report)
return reports, errors.Wrapf(report.Err, "unable to start container %s", name)
}
+ if ctr.AutoRemove {
+ // Defer the removal, so we can return early if needed and
+ // de-spaghetti the code.
+ defer func() {
+ shouldRestart, err := containers.ShouldRestart(ic.ClientCxt, ctr.ID)
+ if err != nil {
+ logrus.Errorf("Failed to check if %s should restart: %v", ctr.ID, err)
+ return
+ }
+
+ if !shouldRestart {
+ if err := containers.Remove(ic.ClientCxt, ctr.ID, bindings.PFalse, bindings.PTrue); err != nil {
+ if errorhandling.Contains(err, define.ErrNoSuchCtr) ||
+ errorhandling.Contains(err, define.ErrCtrRemoved) {
+ logrus.Warnf("Container %s does not exist: %v", ctr.ID, err)
+ } else {
+ logrus.Errorf("Error removing container %s: %v", ctr.ID, err)
+ }
+ }
+ }
+ }()
+ }
+
exitCode, err := containers.Wait(ic.ClientCxt, name, nil)
if err == define.ErrNoSuchCtr {
// Check events
@@ -535,6 +566,16 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
if !ctrRunning {
err = containers.Start(ic.ClientCxt, name, &options.DetachKeys)
if err != nil {
+ if ctr.AutoRemove {
+ if err := containers.Remove(ic.ClientCxt, ctr.ID, bindings.PFalse, bindings.PTrue); err != nil {
+ if errorhandling.Contains(err, define.ErrNoSuchCtr) ||
+ errorhandling.Contains(err, define.ErrCtrRemoved) {
+ logrus.Warnf("Container %s does not exist: %v", ctr.ID, err)
+ } else {
+ logrus.Errorf("Error removing container %s: %v", ctr.ID, err)
+ }
+ }
+ }
report.Err = errors.Wrapf(err, "unable to start container %q", name)
report.ExitCode = define.ExitCode(err)
reports = append(reports, &report)
diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go
index c0df2bb7b..b431fc8bd 100644
--- a/pkg/domain/infra/tunnel/volumes.go
+++ b/pkg/domain/infra/tunnel/volumes.go
@@ -68,8 +68,8 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
return reports, errs, nil
}
-func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
- return volumes.Prune(ic.ClientCxt)
+func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
+ return volumes.Prune(ic.ClientCxt, (map[string][]string)(opts.Filters))
}
func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) {
diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go
index cfdf3ee49..6c26e8708 100644
--- a/pkg/ps/ps.go
+++ b/pkg/ps/ps.go
@@ -179,24 +179,25 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
}
ps := entities.ListContainer{
- Command: conConfig.Command,
- Created: conConfig.CreatedTime,
- Exited: exited,
- ExitCode: exitCode,
- ExitedAt: exitedTime.Unix(),
- ID: conConfig.ID,
- Image: conConfig.RootfsImageName,
- ImageID: conConfig.RootfsImageID,
- IsInfra: conConfig.IsInfra,
- Labels: conConfig.Labels,
- Mounts: ctr.UserVolumes(),
- Names: []string{conConfig.Name},
- Pid: pid,
- Pod: conConfig.Pod,
- Ports: portMappings,
- Size: size,
- StartedAt: startedTime.Unix(),
- State: conState.String(),
+ AutoRemove: ctr.AutoRemove(),
+ Command: conConfig.Command,
+ Created: conConfig.CreatedTime,
+ Exited: exited,
+ ExitCode: exitCode,
+ ExitedAt: exitedTime.Unix(),
+ ID: conConfig.ID,
+ Image: conConfig.RootfsImageName,
+ ImageID: conConfig.RootfsImageID,
+ IsInfra: conConfig.IsInfra,
+ Labels: conConfig.Labels,
+ Mounts: ctr.UserVolumes(),
+ Names: []string{conConfig.Name},
+ Pid: pid,
+ Pod: conConfig.Pod,
+ Ports: portMappings,
+ Size: size,
+ StartedAt: startedTime.Unix(),
+ State: conState.String(),
}
if opts.Pod && len(conConfig.Pod) > 0 {
podName, err := rt.GetName(conConfig.Pod)
diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go
index 169bec2af..231b47974 100644
--- a/pkg/terminal/util.go
+++ b/pkg/terminal/util.go
@@ -61,7 +61,7 @@ func ReadPassword(prompt string) (pw []byte, err error) {
}
}
-func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) {
+func PublicKey(path string, passphrase []byte) (ssh.Signer, error) {
key, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
@@ -75,12 +75,9 @@ func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) {
if len(passphrase) == 0 {
passphrase = ReadPassphrase()
}
- signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
- if err != nil {
- return nil, err
- }
+ return ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
}
- return ssh.PublicKeys(signer), nil
+ return signer, nil
}
func ReadPassphrase() []byte {
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at
index c105a9278..7b500bf57 100644
--- a/test/apiv2/10-images.at
+++ b/test/apiv2/10-images.at
@@ -12,6 +12,8 @@ iid=$(jq -r '.[0].Id' <<<"$output")
t GET libpod/images/$iid/exists 204
t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204
+t GET libpod/images/${iid}abcdef/exists 404 \
+ .cause="failed to find image ${iid}abcdef"
# FIXME: compare to actual podman info
t GET libpod/images/json 200 \
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 5c35edf2b..bc6efc20d 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -5,8 +5,10 @@
# WORKDIR=/data
ENV_WORKDIR_IMG=quay.io/libpod/testimage:20200929
+MultiTagName=localhost/test/testformultitag:tag
podman pull $IMAGE &>/dev/null
+podman tag $IMAGE $MultiTagName
podman pull $ENV_WORKDIR_IMG &>/dev/null
# Unimplemented
#t POST libpod/containers/create '' 201 'sdf'
@@ -216,4 +218,13 @@ t GET containers/$cid/json 200 \
t DELETE containers/$cid 204
+# when the image had multi tags, the container's Image should be correct
+# Fixes https://github.com/containers/podman/issues/8547
+t POST containers/create Image=${MultiTagName} 201 \
+ .Id~[0-9a-f]\\{64\\}
+cid=$(jq -r '.Id' <<<"$output")
+t GET containers/$cid/json 200 \
+ .Image=${MultiTagName}
+t DELETE containers/$cid 204
+t DELETE images/${MultiTagName}?force=true 200
# vim: filetype=sh
diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at
index aa167a97a..2cfca9d08 100644
--- a/test/apiv2/30-volumes.at
+++ b/test/apiv2/30-volumes.at
@@ -20,6 +20,18 @@ t POST libpod/volumes/create \
.Labels.testlabel=testonly \
.Options.type=tmpfs \
.Options.o=nodev,noexec
+t POST libpod/volumes/create \
+ '"Name":"foo3","Label":{"testlabel":""},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \
+ .Name=foo3 \
+ .Labels.testlabel="" \
+ .Options.type=tmpfs \
+ .Options.o=nodev,noexec
+t POST libpod/volumes/create \
+ '"Name":"foo4","Label":{"testlabel1":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \
+ .Name=foo4 \
+ .Labels.testlabel1=testonly \
+ .Options.type=tmpfs \
+ .Options.o=nodev,noexec
# Negative test
# We have created a volume named "foo1"
@@ -39,6 +51,12 @@ t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22foo1%22%5D%7D 200 length
t GET libpod/volumes/json?filters=%7B%22name%22%3A%20%5B%22foo1%22%2C%20%22foo2%22%5D%7D 200 length=2 .[0].Name=foo1 .[1].Name=foo2
# -G --data-urlencode 'filters={"name":["notexist"]}'
t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22notexists%22%5D%7D 200 length=0
+# -G --data-urlencode 'filters={"label":["testlabel"]}'
+t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D 200 length=2
+# -G --data-urlencode 'filters={"label":["testlabel=testonly"]}'
+t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel=testonly%22%5D%7D 200 length=1
+# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}'
+t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D 200 length=1
## inspect volume
t GET libpod/volumes/foo1/json 200 \
@@ -60,6 +78,18 @@ t DELETE libpod/volumes/foo1 404 \
.message~.* \
.response=404
+## Prune volumes with label matching 'testlabel1=testonly'
+# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}'
+t POST libpod/volumes/prune?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D "" 200
+# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}'
+t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D 200 length=0
+
+## Prune volumes with label matching 'testlabel'
+# -G --data-urlencode 'filters={"label":["testlabel"]}'
+t POST libpod/volumes/prune?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D "" 200
+# -G --data-urlencode 'filters={"label":["testlabel"]}'
+t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D 200 length=0
+
## Prune volumes
t POST libpod/volumes/prune "" 200
#After prune volumes, there should be no volume existing
diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at
index 0ce56ee3c..5327bd076 100644
--- a/test/apiv2/35-networks.at
+++ b/test/apiv2/35-networks.at
@@ -50,7 +50,13 @@ t GET networks?filters=%7B%22dangling%22%3A%5B%221%22%5D%7D 500 \
# network inspect docker
t GET networks/a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 200 \
.Name=network1 \
-.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1
+.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 \
+.Scope=local
+
+# network create docker
+t POST networks/create '"Name":"net3","IPAM":{"Config":[]}' 201
+# network delete docker
+t DELETE networks/net3 204
# clean the network
t DELETE libpod/networks/network1 200 \
diff --git a/test/compose/README.md b/test/compose/README.md
new file mode 100644
index 000000000..863decf2c
--- /dev/null
+++ b/test/compose/README.md
@@ -0,0 +1,47 @@
+Tests for docker-compose
+========================
+
+This directory contains tests for docker-compose under podman.
+
+Each subdirectory must contain one docker-compose.yml file along with
+all necessary infrastructure for it (e.g. Containerfile, any files
+to be copied into the container, and so on.
+
+The `test-compose` script will, for each test subdirectory:
+
+* set up a fresh podman root under an empty working directory;
+* run a podman server rooted therein;
+* cd to the test subdirectory, and run `docker-compose up -d`;
+* source `tests.sh`;
+* run `docker-compose down`.
+
+As a special case, `setup.sh` and `teardown.sh` in the test directory
+will contain commands to be executed prior to `docker-compose up` and
+after `docker-compose down` respectively.
+
+tests.sh will probably contain commands of the form
+
+ test_port 12345 = 'hello there'
+
+Where 12345 is the port to curl to; '=' checks equality, '~' uses `expr`
+to check substrings; and 'hello there' is a string to look for in
+the curl results.
+
+Usage:
+
+ $ sudo test/compose/test-compose [pattern]
+
+By default, all subdirs will be run. If given a pattern, only those
+subdirectories matching 'pattern' will be run.
+
+If `$COMPOSE_WAIT` is set, `test-compose` will pause before running
+`docker-compose down`. This can be helpful for you to debug failing tests:
+
+ $ env COMPOSE_WAIT=1 sudo --preserve-env=COMPOSE_WAIT test/compose/test-compose
+
+Then, in another window,
+
+ # ls -lt /var/tmp/
+ # X=/var/tmp/test-compose.tmp.XXXXXX <--- most recent results of above
+ # podman --root $X/root --runroot $X/runroot ps -a
+ # podman --root $X/root --runroot $X/runroot logs -l
diff --git a/test/compose/env_and_volume/README.md b/test/compose/env_and_volume/README.md
new file mode 100644
index 000000000..e7d74976b
--- /dev/null
+++ b/test/compose/env_and_volume/README.md
@@ -0,0 +1,12 @@
+environment variable and volume
+===============
+
+This test creates two containers both of which are running flask. The first container has
+an environment variable called PODMAN_MSG. That container pipes the contents of PODMAN_MSG
+to a file on a shared volume between the containers. The second container then reads the
+file are returns the PODMAN_MSG value via flask (http).
+
+Validation
+------------
+* curl http://localhost:5000 and verify message
+* curl http://localhost:5001 and verify message
diff --git a/test/compose/env_and_volume/docker-compose.yml b/test/compose/env_and_volume/docker-compose.yml
new file mode 100644
index 000000000..df906e170
--- /dev/null
+++ b/test/compose/env_and_volume/docker-compose.yml
@@ -0,0 +1,18 @@
+version: '3'
+services:
+ writer:
+ environment:
+ - PODMAN_MSG=podman_rulez
+ build: write
+ ports:
+ - '5000:5000'
+ volumes:
+ - data:/data
+ reader:
+ build: read
+ ports:
+ - '5001:5000'
+ volumes:
+ - data:/data
+volumes:
+ data:
diff --git a/test/compose/env_and_volume/read/Dockerfile b/test/compose/env_and_volume/read/Dockerfile
new file mode 100644
index 000000000..8d5c45401
--- /dev/null
+++ b/test/compose/env_and_volume/read/Dockerfile
@@ -0,0 +1,5 @@
+FROM quay.io/libpod/podman_python
+WORKDIR /app
+COPY . /app
+ENTRYPOINT ["python3"]
+CMD ["app.py"]
diff --git a/test/compose/env_and_volume/read/app.py b/test/compose/env_and_volume/read/app.py
new file mode 100644
index 000000000..71fbbb26a
--- /dev/null
+++ b/test/compose/env_and_volume/read/app.py
@@ -0,0 +1,10 @@
+from flask import Flask
+app = Flask(__name__)
+
+@app.route('/')
+def hello():
+ f = open("/data/message", "r")
+ return f.read()
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0')
diff --git a/test/compose/env_and_volume/tests.sh b/test/compose/env_and_volume/tests.sh
new file mode 100644
index 000000000..a4c8bed30
--- /dev/null
+++ b/test/compose/env_and_volume/tests.sh
@@ -0,0 +1,4 @@
+# -*- bash -*-
+
+test_port 5000 = "done"
+test_port 5001 = "podman_rulez"
diff --git a/test/compose/env_and_volume/write/Dockerfile b/test/compose/env_and_volume/write/Dockerfile
new file mode 100644
index 000000000..8d5c45401
--- /dev/null
+++ b/test/compose/env_and_volume/write/Dockerfile
@@ -0,0 +1,5 @@
+FROM quay.io/libpod/podman_python
+WORKDIR /app
+COPY . /app
+ENTRYPOINT ["python3"]
+CMD ["app.py"]
diff --git a/test/compose/env_and_volume/write/app.py b/test/compose/env_and_volume/write/app.py
new file mode 100644
index 000000000..b6ad6fe63
--- /dev/null
+++ b/test/compose/env_and_volume/write/app.py
@@ -0,0 +1,13 @@
+from flask import Flask
+import os
+app = Flask(__name__)
+
+@app.route('/')
+def hello():
+ f = open("/data/message", "w")
+ f.write(os.getenv("PODMAN_MSG"))
+ f.close()
+ return "done"
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0')
diff --git a/test/compose/images/README.md b/test/compose/images/README.md
new file mode 100644
index 000000000..f25fbdc24
--- /dev/null
+++ b/test/compose/images/README.md
@@ -0,0 +1,5 @@
+images
+======
+
+Use these directories for images that are needed for the compose testing. These
+images should be then pushed to `quay.io/libpod` for consumption.
diff --git a/test/compose/images/podman-python/Containerfile b/test/compose/images/podman-python/Containerfile
new file mode 100644
index 000000000..47f90afaa
--- /dev/null
+++ b/test/compose/images/podman-python/Containerfile
@@ -0,0 +1,3 @@
+FROM alpine
+WORKDIR /app
+RUN apk update && apk add py3-pip && pip3 install flask && rm -fr /var/cache/apk/*
diff --git a/test/compose/mount_and_label/README.md b/test/compose/mount_and_label/README.md
new file mode 100644
index 000000000..623b38cac
--- /dev/null
+++ b/test/compose/mount_and_label/README.md
@@ -0,0 +1,9 @@
+mount and label
+===============
+
+This test creates a container with a mount (not volume) and also adds a label to the container.
+
+Validation
+------------
+* curl http://localhost:5000 and verify message
+* inspect the container to make the label exists on it
diff --git a/test/compose/mount_and_label/docker-compose.yml b/test/compose/mount_and_label/docker-compose.yml
new file mode 100644
index 000000000..112d7e134
--- /dev/null
+++ b/test/compose/mount_and_label/docker-compose.yml
@@ -0,0 +1,10 @@
+version: '3'
+services:
+ web:
+ build: frontend
+ ports:
+ - '5000:5000'
+ volumes:
+ - /tmp/data:/data:ro
+ labels:
+ - "io.podman=the_best"
diff --git a/test/compose/mount_and_label/frontend/Dockerfile b/test/compose/mount_and_label/frontend/Dockerfile
new file mode 100644
index 000000000..8d5c45401
--- /dev/null
+++ b/test/compose/mount_and_label/frontend/Dockerfile
@@ -0,0 +1,5 @@
+FROM quay.io/libpod/podman_python
+WORKDIR /app
+COPY . /app
+ENTRYPOINT ["python3"]
+CMD ["app.py"]
diff --git a/test/compose/mount_and_label/frontend/app.py b/test/compose/mount_and_label/frontend/app.py
new file mode 100644
index 000000000..bd2794d94
--- /dev/null
+++ b/test/compose/mount_and_label/frontend/app.py
@@ -0,0 +1,10 @@
+from flask import Flask
+app = Flask(__name__)
+
+@app.route('/')
+def hello():
+ f = open("/data/message")
+ return f.read()
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0')
diff --git a/test/compose/mount_and_label/setup.sh b/test/compose/mount_and_label/setup.sh
new file mode 100644
index 000000000..8633d65d5
--- /dev/null
+++ b/test/compose/mount_and_label/setup.sh
@@ -0,0 +1,2 @@
+mkdir -p /tmp/data
+echo "Podman rulez!" > /tmp/data/message
diff --git a/test/compose/mount_and_label/teardown.sh b/test/compose/mount_and_label/teardown.sh
new file mode 100644
index 000000000..57867c28a
--- /dev/null
+++ b/test/compose/mount_and_label/teardown.sh
@@ -0,0 +1 @@
+rm /tmp/data/message
diff --git a/test/compose/mount_and_label/tests.sh b/test/compose/mount_and_label/tests.sh
new file mode 100644
index 000000000..07ff089b5
--- /dev/null
+++ b/test/compose/mount_and_label/tests.sh
@@ -0,0 +1,4 @@
+# -*- bash -*-
+
+test_port 5000 = "Podman rulez!"
+podman container inspect -l --format '{{.Config.Labels}}' | grep "the_best"
diff --git a/test/compose/port_map_diff_port/README.md b/test/compose/port_map_diff_port/README.md
new file mode 100644
index 000000000..13ece72ad
--- /dev/null
+++ b/test/compose/port_map_diff_port/README.md
@@ -0,0 +1,9 @@
+port map on different port
+===============
+
+This test creates a container that runs flask on different ports for the container
+and the host
+
+Validation
+------------
+* curl http://localhost:5001 and verify message
diff --git a/test/compose/port_map_diff_port/docker-compose.yml b/test/compose/port_map_diff_port/docker-compose.yml
new file mode 100644
index 000000000..3003c52f4
--- /dev/null
+++ b/test/compose/port_map_diff_port/docker-compose.yml
@@ -0,0 +1,6 @@
+version: '3'
+services:
+ web:
+ build: frontend
+ ports:
+ - '5001:5000'
diff --git a/test/compose/port_map_diff_port/frontend/Dockerfile b/test/compose/port_map_diff_port/frontend/Dockerfile
new file mode 100644
index 000000000..8d5c45401
--- /dev/null
+++ b/test/compose/port_map_diff_port/frontend/Dockerfile
@@ -0,0 +1,5 @@
+FROM quay.io/libpod/podman_python
+WORKDIR /app
+COPY . /app
+ENTRYPOINT ["python3"]
+CMD ["app.py"]
diff --git a/test/compose/port_map_diff_port/frontend/app.py b/test/compose/port_map_diff_port/frontend/app.py
new file mode 100644
index 000000000..895556a89
--- /dev/null
+++ b/test/compose/port_map_diff_port/frontend/app.py
@@ -0,0 +1,9 @@
+from flask import Flask
+app = Flask(__name__)
+
+@app.route('/')
+def hello():
+ return "Podman rulez!"
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0')
diff --git a/test/compose/port_map_diff_port/tests.sh b/test/compose/port_map_diff_port/tests.sh
new file mode 100644
index 000000000..5a468aadc
--- /dev/null
+++ b/test/compose/port_map_diff_port/tests.sh
@@ -0,0 +1,3 @@
+# -*- bash -*-
+
+test_port 5001 = "Podman rulez!"
diff --git a/test/compose/setup.sh.example b/test/compose/setup.sh.example
new file mode 100644
index 000000000..9004b1e76
--- /dev/null
+++ b/test/compose/setup.sh.example
@@ -0,0 +1,3 @@
+# -*- bash -*-
+
+export ENV_PASSTHRU=$(random_string 20)
diff --git a/test/compose/simple_port_map/README.md b/test/compose/simple_port_map/README.md
new file mode 100644
index 000000000..f28d71c3e
--- /dev/null
+++ b/test/compose/simple_port_map/README.md
@@ -0,0 +1,9 @@
+simple port map to host
+===============
+
+This test creates a container that runs flask on and maps to the same
+host port
+
+Validation
+------------
+* curl http://localhost:5000 and verify message
diff --git a/test/compose/simple_port_map/docker-compose.yml b/test/compose/simple_port_map/docker-compose.yml
new file mode 100644
index 000000000..e7eab1047
--- /dev/null
+++ b/test/compose/simple_port_map/docker-compose.yml
@@ -0,0 +1,6 @@
+version: '3'
+services:
+ web:
+ build: frontend
+ ports:
+ - '5000:5000'
diff --git a/test/compose/simple_port_map/frontend/Dockerfile b/test/compose/simple_port_map/frontend/Dockerfile
new file mode 100644
index 000000000..2595828ff
--- /dev/null
+++ b/test/compose/simple_port_map/frontend/Dockerfile
@@ -0,0 +1,6 @@
+FROM alpine
+WORKDIR /app
+RUN apk update && apk add py3-pip && pip3 install flask
+COPY . /app
+ENTRYPOINT ["python3"]
+CMD ["app.py"]
diff --git a/test/compose/simple_port_map/frontend/app.py b/test/compose/simple_port_map/frontend/app.py
new file mode 100644
index 000000000..e4f84068c
--- /dev/null
+++ b/test/compose/simple_port_map/frontend/app.py
@@ -0,0 +1,10 @@
+from flask import Flask
+import os
+app = Flask(__name__)
+
+@app.route('/')
+def hello():
+ return "Podman rulez!"
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0')
diff --git a/test/compose/simple_port_map/tests.sh b/test/compose/simple_port_map/tests.sh
new file mode 100644
index 000000000..ccb2b6a3d
--- /dev/null
+++ b/test/compose/simple_port_map/tests.sh
@@ -0,0 +1,3 @@
+# -*- bash -*-
+
+test_port 5000 = "Podman rulez!"
diff --git a/test/compose/teardown.sh.example b/test/compose/teardown.sh.example
new file mode 100644
index 000000000..3f8153fa0
--- /dev/null
+++ b/test/compose/teardown.sh.example
@@ -0,0 +1,4 @@
+# -*- bash -*-
+
+# FIXME: this is completely unnecessary; it's just an example of a teardown
+unset ENV_PASSTHRU
diff --git a/test/compose/test-compose b/test/compose/test-compose
new file mode 100755
index 000000000..9558fbf58
--- /dev/null
+++ b/test/compose/test-compose
@@ -0,0 +1,345 @@
+#!/usr/bin/env bash
+#
+# Usage: test-compose [testname]
+#
+ME=$(basename $0)
+
+###############################################################################
+# BEGIN stuff you can but probably shouldn't customize
+
+# Directory where this script and all subtests live
+TEST_ROOTDIR=$(realpath $(dirname $0))
+
+# Podman executable
+PODMAN_BIN=$(realpath $TEST_ROOTDIR/../../bin)/podman
+
+# Local path to docker socket (we will add the unix:/ prefix when we need it)
+DOCKER_SOCK=/var/run/docker.sock
+
+# END stuff you can but probably shouldn't customize
+###############################################################################
+# BEGIN setup
+
+export TMPDIR=${TMPDIR:-/var/tmp}
+WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX)
+
+# Log of all HTTP requests and responses; always make '.log' point to latest
+LOGBASE=${TMPDIR}/$ME.log
+LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S')
+ln -sf $LOG $LOGBASE
+
+# Keep track of test count and failures in files, not variables, because
+# variables don't carry back up from subshells.
+testcounter_file=$WORKDIR/.testcounter
+failures_file=$WORKDIR/.failures
+
+echo 0 >$testcounter_file
+echo 0 >$failures_file
+
+# END setup
+###############################################################################
+# BEGIN infrastructure code - the helper functions used in tests themselves
+
+#########
+# die # Exit error with a message to stderr
+#########
+function die() {
+ echo "$ME: $*" >&2
+ exit 1
+}
+
+########
+# is # Simple comparison
+########
+function is() {
+ local actual=$1
+ local expect=$2
+ local testname=$3
+
+ if [[ $actual = $expect ]]; then
+ # On success, include expected value; this helps readers understand
+ _show_ok 1 "$testname=$expect"
+ return
+ fi
+ _show_ok 0 "$testname" "$expect" "$actual"
+}
+
+##########
+# like # Compare, but allowing patterns
+##########
+function like() {
+ local actual=$1
+ local expect=$2
+ local testname=$3
+
+ # "is" (equality) is a subset of "like", but one that expr fails on if
+ # the expected result has shell-special characters like '['. Treat it
+ # as a special case.
+
+ if [[ "$actual" = "$expect" ]]; then
+ _show_ok 1 "$testname=$expect"
+ return
+ fi
+
+ if expr "$actual" : ".*$expect" &>/dev/null; then
+ # On success, include expected value; this helps readers understand
+ _show_ok 1 "$testname ('$actual') ~ $expect"
+ return
+ fi
+ _show_ok 0 "$testname" "~ $expect" "$actual"
+}
+
+##############
+# _show_ok # Helper for is() and like(): displays 'ok' or 'not ok'
+##############
+function _show_ok() {
+ local ok=$1
+ local testname=$2
+
+ # If output is a tty, colorize pass/fail
+ local red=
+ local green=
+ local reset=
+ local bold=
+ if [ -t 1 ]; then
+ red='\e[31m'
+ green='\e[32m'
+ reset='\e[0m'
+ bold='\e[1m'
+ fi
+
+ _bump $testcounter_file
+ count=$(<$testcounter_file)
+
+ # "skip" is a special case of "ok". Assume that our caller has included
+ # the magical '# skip - reason" comment string.
+ if [[ $ok == "skip" ]]; then
+ # colon-plus: replace green with yellow, but only if green is non-null
+ green="${green:+\e[33m}"
+ ok=1
+ fi
+ if [ $ok -eq 1 ]; then
+ echo -e "${green}ok $count $testname${reset}"
+ echo "ok $count $testname" >>$LOG
+ return
+ fi
+
+ # Failed
+ local expect=$3
+ local actual=$4
+ printf "${red}not ok $count $testname${reset}\n"
+ printf "${red}# expected: %s${reset}\n" "$expect"
+ printf "${red}# actual: ${bold}%s${reset}\n" "$actual"
+
+ echo "not ok $count $testname" >>$LOG
+ echo " expected: $expect" >>$LOG
+
+ _bump $failures_file
+}
+
+###########
+# _bump # Increment a counter in a file
+###########
+function _bump() {
+ local file=$1
+
+ count=$(<$file)
+ echo $(( $count + 1 )) >| $file
+}
+
+###############
+# test_port # Run curl against a port, check results against expectation
+###############
+function test_port() {
+ local port="$1" # e.g. 5000
+ local op="$2" # '=' or '~'
+ local expect="$3" # what to expect from curl output
+
+ local actual=$(curl --retry 5 --retry-connrefused -s http://127.0.0.1:$port/)
+ local curl_rc=$?
+ if [ $curl_rc -ne 0 ]; then
+ _show_ok 0 "$testname - curl failed with status $curl_rc"
+### docker-compose down >>$logfile 2>&1
+### exit 1
+ fi
+
+ case "$op" in
+ '=') is "$actual" "$expect" "$testname : port $port" ;;
+ '~') like "$actual" "$expect" "$testname : port $port" ;;
+ *) die "Invalid operator '$op'" ;;
+ esac
+}
+
+
+###################
+# start_service # Run the socket listener
+###################
+service_pid=
+function start_service() {
+ test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN"
+
+ # FIXME: use ${testname} subdir but we can't: 50-char limit in runroot
+ rm -rf $WORKDIR/{root,runroot,cni}
+ mkdir --mode 0755 $WORKDIR/{root,runroot,cni}
+ chcon --reference=/var/lib/containers $WORKDIR/root
+ cp /etc/cni/net.d/*podman*conflist $WORKDIR/cni/
+
+ $PODMAN_BIN \
+ --root $WORKDIR/root \
+ --runroot $WORKDIR/runroot \
+ --cgroup-manager=systemd \
+ --cni-config-dir $WORKDIR/cni \
+ system service \
+ --time 0 unix:/$DOCKER_SOCK \
+ &> $WORKDIR/server.log &
+ service_pid=$!
+
+ # Wait (FIXME: how do we test the socket?)
+ local _timeout=5
+ while [ $_timeout -gt 0 ]; do
+ # FIXME: should we actually try a read or write?
+ test -S $DOCKER_SOCK && return
+ sleep 1
+ _timeout=$(( $_timeout - 1 ))
+ done
+ cat $WORKDIR/server.log
+ die "Timed out waiting for service"
+}
+
+############
+# podman # Needed by some test scripts to invoke the actual podman binary
+############
+function podman() {
+ echo "\$ podman $*" >>$WORKDIR/output.log
+ $PODMAN_BIN \
+ --root $WORKDIR/root \
+ --runroot $WORKDIR/runroot \
+ "$@" >>$WORKDIR/output.log 2>&1
+}
+
+###################
+# random_string # Returns a pseudorandom human-readable string
+###################
+function random_string() {
+ # Numeric argument, if present, is desired length of string
+ local length=${1:-10}
+
+ head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
+}
+
+# END infrastructure code
+###############################################################################
+# BEGIN sanity checks
+
+for tool in curl docker-compose; do
+ type $tool &>/dev/null || die "$ME: Required tool '$tool' not found"
+done
+
+# END sanity checks
+###############################################################################
+# BEGIN entry handler (subtest invoker)
+
+# Identify the tests to run. If called with args, use those as globs.
+tests_to_run=()
+if [ -n "$*" ]; then
+ shopt -s nullglob
+ for i; do
+ match=(${TEST_ROOTDIR}/*${i}*/docker-compose.yml)
+ if [ ${#match} -eq 0 ]; then
+ die "No match for $TEST_ROOTDIR/*$i*.curl"
+ fi
+ tests_to_run+=("${match[@]}")
+ done
+ shopt -u nullglob
+else
+ tests_to_run=(${TEST_ROOTDIR}/*/docker-compose.yml)
+fi
+
+# Too hard to precompute the number of tests; just spit it out at the end.
+n_tests=0
+for t in ${tests_to_run[@]}; do
+ testdir="$(dirname $t)"
+ testname="$(basename $testdir)"
+
+ if [ -e $test_dir/SKIP ]; then
+ local reason="$(<$test_dir/SKIP)"
+ if [ -n "$reason" ]; then
+ reason=" - $reason"
+ fi
+ _show_ok skip "$testname # skip$reason"
+ continue
+ fi
+
+ start_service
+
+ logfile=$WORKDIR/$testname.log
+ (
+ cd $testdir || die "Cannot cd $testdir"
+
+ # setup file may be used for creating temporary directories/files.
+ # We source it so that envariables defined in it will get back to us.
+ if [ -e setup.sh ]; then
+ . setup.sh
+ fi
+ if [ -e teardown.sh ]; then
+ trap '. teardown.sh' 0
+ fi
+
+ docker-compose up -d &> $logfile
+ docker_compose_rc=$?
+ if [[ $docker_compose_rc -ne 0 ]]; then
+ _show_ok 0 "$testname - up" "[ok]" "status=$docker_compose_rc"
+ sed -e 's/^/# /' <$logfile
+ docker-compose down >>$logfile 2>&1 # No status check here
+ exit 1
+ fi
+ _show_ok 1 "$testname - up"
+
+ # Run tests. This is likely to be a series of 'test_port' checks
+ # but may also include podman commands to inspect labels, state
+ if [ -e tests.sh ]; then
+ . tests.sh
+ fi
+ # FIXME: if any tests fail, try 'podman logs' on container?
+
+ if [ -n "$COMPOSE_WAIT" ]; then
+ echo -n "Pausing due to \$COMPOSE_WAIT. Press ENTER to continue: "
+ read keepgoing
+ fi
+
+ # Done. Clean up.
+ docker-compose down &> $logfile
+ rc=$?
+ if [[ $rc -eq 0 ]]; then
+ _show_ok 1 "$testname - down"
+ else
+ _show_ok 0 "$testname - down" "[ok]" "rc=$rc"
+ # FIXME: show error
+ fi
+ )
+
+ kill $service_pid
+ wait $service_pid
+
+ # FIXME: otherwise we get EBUSY
+ umount $WORKDIR/root/overlay &>/dev/null
+
+ # FIXME: run 'podman ps'?
+# rm -rf $WORKDIR/${testname}
+done
+
+# END entry handler
+###############################################################################
+
+# Clean up
+
+test_count=$(<$testcounter_file)
+failure_count=$(<$failures_file)
+
+#if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then
+# rm -rf $WORKDIR
+#fi
+
+echo "1..${test_count}"
+
+exit $failure_count
diff --git a/test/e2e/image_sign_test.go b/test/e2e/image_sign_test.go
index c9041eaba..57739419c 100644
--- a/test/e2e/image_sign_test.go
+++ b/test/e2e/image_sign_test.go
@@ -1,6 +1,7 @@
package integration
import (
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -58,4 +59,19 @@ var _ = Describe("Podman image sign", func() {
_, err = os.Stat(filepath.Join(sigDir, "library"))
Expect(err).To(BeNil())
})
+
+ It("podman sign --all multi-arch image", func() {
+ cmd := exec.Command("gpg", "--import", "sign/secret-key.asc")
+ err := cmd.Run()
+ Expect(err).To(BeNil())
+ sigDir := filepath.Join(podmanTest.TempDir, "test-sign-multi")
+ err = os.MkdirAll(sigDir, os.ModePerm)
+ Expect(err).To(BeNil())
+ session := podmanTest.Podman([]string{"image", "sign", "--all", "--directory", sigDir, "--sign-by", "foo@bar.com", "docker://library/alpine"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ fInfos, err := ioutil.ReadDir(filepath.Join(sigDir, "library"))
+ Expect(err).To(BeNil())
+ Expect(len(fInfos) > 1).To(BeTrue())
+ })
})
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index a749a86ff..aae6d4f02 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -355,4 +355,21 @@ var _ = Describe("Podman logs", func() {
Expect(outlines[0]).To(Equal("1\r"))
Expect(outlines[1]).To(Equal("2\r"))
})
+
+ It("podman logs test stdout and stderr", func() {
+ cname := "log-test"
+ logc := podmanTest.Podman([]string{"run", "--name", cname, ALPINE, "sh", "-c", "echo stdout; echo stderr >&2"})
+ logc.WaitWithDefaultTimeout()
+ Expect(logc).To(Exit(0))
+
+ wait := podmanTest.Podman([]string{"wait", cname})
+ wait.WaitWithDefaultTimeout()
+ Expect(wait).To(Exit(0))
+
+ results := podmanTest.Podman([]string{"logs", cname})
+ results.WaitWithDefaultTimeout()
+ Expect(results).To(Exit(0))
+ Expect(results.OutputToString()).To(Equal("stdout"))
+ Expect(results.ErrorToString()).To(Equal("stderr"))
+ })
})
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 5ecfdd6b5..3a2387559 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -1072,7 +1072,7 @@ var _ = Describe("Podman play kube", func() {
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
- Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
+ Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted"))
})
It("podman play kube seccomp pod level", func() {
@@ -1099,7 +1099,7 @@ var _ = Describe("Podman play kube", func() {
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
- Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
+ Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted"))
})
It("podman play kube with pull policy of never should be 125", func() {
diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go
index 942e00123..a6f22e007 100644
--- a/test/e2e/start_test.go
+++ b/test/e2e/start_test.go
@@ -49,6 +49,29 @@ var _ = Describe("Podman start", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman start --rm removed on failure", func() {
+ session := podmanTest.Podman([]string{"create", "--name=test", "--rm", ALPINE, "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"start", "test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ session = podmanTest.Podman([]string{"container", "exists", "test"})
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman start --rm --attach removed on failure", func() {
+ session := podmanTest.Podman([]string{"create", "--rm", ALPINE, "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+ session = podmanTest.Podman([]string{"start", "--attach", cid})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ session = podmanTest.Podman([]string{"container", "exists", cid})
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
It("podman container start single container by id", func() {
session := podmanTest.Podman([]string{"container", "create", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go
index 7393b13cb..6f04ce48c 100644
--- a/test/e2e/toolbox_test.go
+++ b/test/e2e/toolbox_test.go
@@ -239,7 +239,7 @@ var _ = Describe("Toolbox-specific testing", func() {
session = podmanTest.Podman([]string{"logs", "test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring(expectedOutput))
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedOutput))
})
It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() {
diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go
index c8521ebe7..a910c47a7 100644
--- a/test/e2e/volume_prune_test.go
+++ b/test/e2e/volume_prune_test.go
@@ -62,6 +62,66 @@ var _ = Describe("Podman volume prune", func() {
podmanTest.Cleanup()
})
+ It("podman prune volume --filter", func() {
+ session := podmanTest.Podman([]string{"volume", "create", "--label", "label1=value1", "myvol1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv1", "myvol2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv2", "myvol3"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1", "myvol4"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"create", "-v", "myvol5:/myvol5", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"create", "-v", "myvol6:/myvol6", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(7))
+
+ session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=label1=value1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(6))
+
+ session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=sharedlabel1=slv1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(5))
+
+ session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=sharedlabel1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(3))
+
+ podmanTest.Cleanup()
+ })
+
It("podman system prune --volume", func() {
session := podmanTest.Podman([]string{"volume", "create"})
session.WaitWithDefaultTimeout()