summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile17
-rw-r--r--OWNERS14
-rw-r--r--cmd/podman/common/completion.go10
-rw-r--r--cmd/podman/common/create.go8
-rw-r--r--cmd/podman/common/create_opts.go16
-rw-r--r--cmd/podman/common/specgen.go14
-rw-r--r--cmd/podman/completion/completion.go6
-rw-r--r--cmd/podman/containers/cp.go19
-rw-r--r--cmd/podman/containers/create.go16
-rw-r--r--cmd/podman/images/list.go4
-rw-r--r--cmd/podman/images/pull.go20
-rw-r--r--cmd/podman/networks/create.go21
-rw-r--r--cmd/podman/networks/list.go38
-rw-r--r--cmd/podman/root.go2
-rw-r--r--docs/source/markdown/podman-cp.1.md30
-rw-r--r--docs/source/markdown/podman-create.1.md5
-rw-r--r--docs/source/markdown/podman-load.1.md2
-rw-r--r--docs/source/markdown/podman-network-connect.1.md2
-rw-r--r--docs/source/markdown/podman-network-create.1.md12
-rw-r--r--docs/source/markdown/podman-network-disconnect.1.md2
-rw-r--r--docs/source/markdown/podman-network-inspect.1.md2
-rw-r--r--docs/source/markdown/podman-network-ls.1.md41
-rw-r--r--docs/source/markdown/podman-network.1.md2
-rw-r--r--docs/source/markdown/podman-pull.1.md5
-rw-r--r--docs/source/markdown/podman-run.1.md39
-rw-r--r--go.mod9
-rw-r--r--go.sum13
-rwxr-xr-xhack/podman-socat122
-rw-r--r--install.md4
-rw-r--r--libpod/container.go5
-rw-r--r--libpod/container_inspect.go6
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/container_internal_linux.go26
-rw-r--r--libpod/events/config.go2
-rw-r--r--libpod/image/image.go25
-rw-r--r--libpod/info.go1
-rw-r--r--libpod/network/create.go52
-rw-r--r--libpod/network/files.go48
-rw-r--r--libpod/network/netconflist.go92
-rw-r--r--libpod/network/network.go11
-rw-r--r--libpod/networking_linux.go119
-rw-r--r--libpod/oci_conmon_exec_linux.go2
-rw-r--r--libpod/oci_conmon_linux.go8
-rw-r--r--libpod/options.go13
-rw-r--r--libpod/plugin/volume_api.go454
-rw-r--r--libpod/reset.go2
-rw-r--r--libpod/runtime.go15
-rw-r--r--libpod/runtime_cstorage.go6
-rw-r--r--libpod/runtime_img.go52
-rw-r--r--libpod/runtime_migrate.go6
-rw-r--r--libpod/runtime_migrate_unsupported.go2
-rw-r--r--libpod/runtime_pod_infra_linux.go67
-rw-r--r--libpod/runtime_pod_linux.go4
-rw-r--r--pkg/api/handlers/compat/containers.go7
-rw-r--r--pkg/api/handlers/compat/containers_archive.go319
-rw-r--r--pkg/api/handlers/compat/containers_create.go6
-rw-r--r--pkg/api/handlers/compat/containers_pause.go2
-rw-r--r--pkg/api/handlers/compat/containers_restart.go2
-rw-r--r--pkg/api/handlers/compat/containers_start.go4
-rw-r--r--pkg/api/handlers/compat/containers_stop.go4
-rw-r--r--pkg/api/handlers/compat/containers_unpause.go2
-rw-r--r--pkg/api/handlers/compat/images.go2
-rw-r--r--pkg/api/handlers/compat/images_build.go8
-rw-r--r--pkg/api/handlers/compat/images_push.go1
-rw-r--r--pkg/api/handlers/compat/networks.go64
-rw-r--r--pkg/api/handlers/compat/ping.go1
-rw-r--r--pkg/api/handlers/compat/system.go82
-rw-r--r--pkg/api/handlers/compat/volumes.go4
-rw-r--r--pkg/api/handlers/libpod/images.go2
-rw-r--r--pkg/api/handlers/libpod/networks.go4
-rw-r--r--pkg/api/handlers/types.go71
-rw-r--r--pkg/api/server/register_networks.go17
-rw-r--r--pkg/bindings/connection.go10
-rw-r--r--pkg/bindings/containers/attach.go4
-rw-r--r--pkg/bindings/network/network.go9
-rw-r--r--pkg/bindings/test/containers_test.go8
-rw-r--r--pkg/copy/copy.go188
-rw-r--r--pkg/copy/item.go601
-rw-r--r--pkg/domain/entities/containers.go10
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/entities/images.go22
-rw-r--r--pkg/domain/entities/network.go10
-rw-r--r--pkg/domain/infra/abi/cp.go439
-rw-r--r--pkg/domain/infra/abi/images.go149
-rw-r--r--pkg/domain/infra/abi/images_list.go13
-rw-r--r--pkg/domain/infra/abi/network.go45
-rw-r--r--pkg/domain/infra/abi/system.go17
-rw-r--r--pkg/domain/infra/tunnel/containers.go4
-rw-r--r--pkg/specgen/generate/config_linux.go53
-rw-r--r--pkg/specgen/generate/container_create.go3
-rw-r--r--pkg/specgen/generate/namespaces.go2
-rw-r--r--pkg/specgen/generate/oci.go2
-rw-r--r--pkg/specgen/namespaces.go16
-rw-r--r--pkg/specgen/specgen.go7
-rw-r--r--pkg/systemd/generate/pods.go2
-rw-r--r--pkg/util/utils.go5
-rw-r--r--pkg/util/utils_supported.go13
-rw-r--r--pkg/util/utils_windows.go6
-rw-r--r--test/apiv2/20-containers.at2
-rw-r--r--test/apiv2/25-containersMore.at9
-rw-r--r--test/apiv2/35-networks.at29
-rw-r--r--test/apiv2/rest_api/test_rest_v2_0_0.py254
-rw-r--r--test/e2e/build_test.go2
-rw-r--r--test/e2e/common_test.go2
-rw-r--r--test/e2e/config/containers.conf4
-rw-r--r--test/e2e/containers_conf_test.go19
-rw-r--r--test/e2e/cp_test.go322
-rw-r--r--test/e2e/create_test.go32
-rw-r--r--test/e2e/images_test.go14
-rw-r--r--test/e2e/import_test.go2
-rw-r--r--test/e2e/network_create_test.go33
-rw-r--r--test/e2e/network_test.go93
-rw-r--r--test/e2e/port_test.go12
-rw-r--r--test/e2e/pull_test.go30
-rw-r--r--test/e2e/run_dns_test.go12
-rw-r--r--test/e2e/run_entrypoint_test.go6
-rw-r--r--test/e2e/run_networking_test.go23
-rw-r--r--test/e2e/run_test.go33
-rw-r--r--test/system/010-images.bats13
-rw-r--r--test/system/030-run.bats37
-rw-r--r--test/system/035-logs.bats3
-rw-r--r--test/system/065-cp.bats397
-rw-r--r--test/system/120-load.bats30
-rw-r--r--test/system/400-unprivileged-access.bats2
-rw-r--r--test/utils/podmansession_test.go4
-rw-r--r--test/utils/utils.go2
-rw-r--r--troubleshooting.md5
-rw-r--r--utils/utils_supported.go45
-rw-r--r--vendor/github.com/containers/common/pkg/config/config.go15
-rw-r--r--vendor/github.com/containers/common/pkg/config/containers.conf16
-rw-r--r--vendor/github.com/containers/common/pkg/config/default.go6
-rw-r--r--vendor/github.com/containers/common/pkg/config/libpodConfig.go407
-rw-r--r--vendor/github.com/containers/common/version/version.go2
-rw-r--r--vendor/github.com/coreos/go-systemd/LICENSE191
-rw-r--r--vendor/github.com/coreos/go-systemd/NOTICE5
-rw-r--r--vendor/github.com/coreos/go-systemd/activation/files.go67
-rw-r--r--vendor/github.com/coreos/go-systemd/activation/listeners.go103
-rw-r--r--vendor/github.com/coreos/go-systemd/activation/packetconns.go38
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go6
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/LICENSE202
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/NOTICE19
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go37
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/handler.go88
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/pool.go18
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go58
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go34
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go35
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go10
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go45
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go16
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go70
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go20
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go13
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/volume/README.md36
-rw-r--r--vendor/github.com/docker/go-plugins-helpers/volume/api.go230
-rw-r--r--vendor/github.com/spf13/cobra/bash_completions.go752
-rw-r--r--vendor/github.com/spf13/cobra/custom_completions.go101
-rw-r--r--vendor/github.com/spf13/cobra/fish_completions.go115
-rw-r--r--vendor/github.com/spf13/cobra/zsh_completions.go55
-rw-r--r--vendor/modules.txt11
160 files changed, 5656 insertions, 2393 deletions
diff --git a/Makefile b/Makefile
index 3e1401b01..610449e9d 100644
--- a/Makefile
+++ b/Makefile
@@ -460,15 +460,6 @@ changelog: ## Generate changelog
$(shell cat $(TMPFILE) >> changelog.txt)
$(shell rm $(TMPFILE))
-completions: binaries
- install ${SELINUXOPT} -d -m 755 completions/{bash,zsh,fish}
- ./bin/podman completion bash --no-desc -f completions/bash/podman
- ./bin/podman-remote completion bash --no-desc -f completions/bash/podman-remote
- ./bin/podman completion zsh -f completions/zsh/_podman
- ./bin/podman-remote completion zsh -f completions/zsh/_podman-remote
- ./bin/podman completion fish -f completions/fish/podman.fish
- ./bin/podman-remote completion fish -f completions/fish/podman-remote.fish
-
.PHONY: install
install: .gopathok install.bin install.remote install.man install.cni install.systemd ## Install binaries to system locations
@@ -620,14 +611,6 @@ install.libseccomp.sudo:
.PHONY: validate.completions
validate.completions: SHELL:=/usr/bin/env bash # Set shell to bash for this target
validate.completions:
- # Check that nobody has manually edited the completion scripts
- # If this check fails run make completions to restore the correct scripts
- diff completions/bash/podman <(./bin/podman completion --no-desc bash)
- diff completions/zsh/_podman <(./bin/podman completion zsh)
- diff completions/fish/podman.fish <(./bin/podman completion fish)
- diff completions/bash/podman-remote <(./bin/podman-remote completion --no-desc bash)
- diff completions/zsh/_podman-remote <(./bin/podman-remote completion zsh)
- diff completions/fish/podman-remote.fish <(./bin/podman-remote completion fish)
# Check if the files can be loaded by the shell
. completions/bash/podman
if [ -x /bin/zsh ]; then /bin/zsh completions/zsh/_podman; fi
diff --git a/OWNERS b/OWNERS
index 7cbef7f37..88dedae25 100644
--- a/OWNERS
+++ b/OWNERS
@@ -3,20 +3,26 @@ approvers:
- edsantiago
- giuseppe
- jwhonce
+ - Luap99
- mheon
- rhatdan
+ - saschagrunert
- TomSweeneyRedHat
- - vrothberg
- umohnani8
+ - vrothberg
+ - zhangguanzhang
reviewers:
+ - ashley-cui
- baude
- edsantiago
- giuseppe
- jwhonce
+ - Luap99
- mheon
+ - QiWang19
- rhatdan
+ - saschagrunert
- TomSweeneyRedHat
- - vrothberg
- - ashley-cui
- - QiWang19
- umohnani8
+ - vrothberg
+ - zhangguanzhang
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go
index 9856e46ef..25f4d0f79 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -22,7 +22,7 @@ var (
// ChangeCmds is the list of valid Change commands to passed to the Commit call
ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"}
// LogLevels supported by podman
- LogLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"}
+ LogLevels = []string{"debug", "info", "warn", "warning", "error", "fatal", "panic"}
)
type completeType int
@@ -861,10 +861,10 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string)
"status=": func(_ string) ([]string, cobra.ShellCompDirective) {
return containerStatuses, cobra.ShellCompDirectiveNoFileComp
},
- "ancestor": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
- "before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
- "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
- "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
+ "ancestor=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
+ "before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
+ "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
+ "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
"health=": func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{define.HealthCheckHealthy,
define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 599b430ea..14086ace4 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -513,6 +513,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) {
)
_ = cmd.RegisterFlagCompletionFunc(pidsLimitFlagName, completion.AutocompleteNone)
+ platformFlagName := "platform"
+ createFlags.StringVar(
+ &cf.Platform,
+ platformFlagName, "",
+ "Specify the platform for selecting the image. (Conflicts with override-arch and override-os)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone)
+
podFlagName := "pod"
createFlags.StringVar(
&cf.Pod,
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 6dc43dbc6..4b0e40df2 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -78,6 +78,7 @@ type ContainerCLIOpts struct {
OverrideVariant string
PID string
PIDsLimit *int64
+ Platform string
Pod string
PodIDFile string
PreserveFDs uint
@@ -203,20 +204,14 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
for _, m := range cc.HostConfig.Mounts {
mount := fmt.Sprintf("type=%s", m.Type)
if len(m.Source) > 0 {
- mount += fmt.Sprintf("source=%s", m.Source)
+ mount += fmt.Sprintf(",source=%s", m.Source)
}
if len(m.Target) > 0 {
- mount += fmt.Sprintf("dest=%s", m.Target)
+ mount += fmt.Sprintf(",dst=%s", m.Target)
}
mounts = append(mounts, mount)
}
- // volumes
- volumes := make([]string, 0, len(cc.Config.Volumes))
- for v := range cc.Config.Volumes {
- volumes = append(volumes, v)
- }
-
// dns
dns := make([]net.IP, 0, len(cc.HostConfig.DNS))
for _, d := range cc.HostConfig.DNS {
@@ -373,7 +368,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
UserNS: string(cc.HostConfig.UsernsMode),
UTS: string(cc.HostConfig.UTSMode),
Mount: mounts,
- Volume: volumes,
VolumesFrom: cc.HostConfig.VolumesFrom,
Workdir: cc.Config.WorkingDir,
Net: &netInfo,
@@ -388,6 +382,10 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
}
}
+ // volumes
+ if volumes := cc.HostConfig.Binds; len(volumes) > 0 {
+ cliOpts.Volume = volumes
+ }
if len(cc.HostConfig.BlkioWeightDevice) > 0 {
devices := make([]string, 0, len(cc.HostConfig.BlkioWeightDevice))
for _, d := range cc.HostConfig.BlkioWeightDevice {
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 0bb6e79e5..e0da142ad 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -517,18 +517,22 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
}
switch con[0] {
- case "proc-opts":
- s.ProcOpts = strings.Split(con[1], ",")
+ case "apparmor":
+ s.ContainerSecurityConfig.ApparmorProfile = con[1]
+ s.Annotations[define.InspectAnnotationApparmor] = con[1]
case "label":
// TODO selinux opts and label opts are the same thing
s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
- case "apparmor":
- s.ContainerSecurityConfig.ApparmorProfile = con[1]
- s.Annotations[define.InspectAnnotationApparmor] = con[1]
+ case "mask":
+ s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...)
+ case "proc-opts":
+ s.ProcOpts = strings.Split(con[1], ",")
case "seccomp":
s.SeccompProfilePath = con[1]
s.Annotations[define.InspectAnnotationSeccomp] = con[1]
+ case "unmask":
+ s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, strings.Split(con[1], ":")...)
default:
return fmt.Errorf("invalid --security-opt 2: %q", opt)
}
diff --git a/cmd/podman/completion/completion.go b/cmd/podman/completion/completion.go
index ead8d1f05..84942a508 100644
--- a/cmd/podman/completion/completion.go
+++ b/cmd/podman/completion/completion.go
@@ -67,11 +67,7 @@ func completion(cmd *cobra.Command, args []string) error {
var err error
switch args[0] {
case "bash":
- if noDesc {
- err = cmd.Root().GenBashCompletion(w)
- } else {
- err = cmd.Root().GenBashCompletionWithDesc(w)
- }
+ err = cmd.Root().GenBashCompletion(w)
case "zsh":
if noDesc {
err = cmd.Root().GenZshCompletionNoDesc(w)
diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go
index a74ea89d2..fd3aa7680 100644
--- a/cmd/podman/containers/cp.go
+++ b/cmd/podman/containers/cp.go
@@ -3,10 +3,7 @@ package containers
import (
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
- "github.com/containers/podman/v2/pkg/cgroups"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/rootless"
- "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -43,7 +40,7 @@ var (
func cpFlags(cmd *cobra.Command) {
flags := cmd.Flags()
flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.")
- flags.BoolVar(&cpOpts.Pause, "pause", copyPause(), "Pause the container while copying")
+ flags.BoolVar(&cpOpts.Pause, "pause", true, "Pause the container while copying")
}
func init() {
@@ -62,17 +59,5 @@ func init() {
}
func cp(cmd *cobra.Command, args []string) error {
- _, err := registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts)
- return err
-}
-
-func copyPause() bool {
- if rootless.IsRootless() {
- cgroupv2, _ := cgroups.IsCgroup2UnifiedMode()
- if !cgroupv2 {
- logrus.Debugf("defaulting to pause==false on rootless cp in cgroupv1 systems")
- return false
- }
- }
- return true
+ return registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts)
}
diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go
index db920554e..3d87c71a9 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -18,6 +18,7 @@ import (
"github.com/containers/podman/v2/pkg/specgen"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -236,6 +237,21 @@ func pullImage(imageName string) (string, error) {
imageMissing = !br.Value
}
+ if cliVals.Platform != "" {
+ if cliVals.OverrideArch != "" || cliVals.OverrideOS != "" {
+ return "", errors.Errorf("--platform option can not be specified with --overide-arch or --override-os")
+ }
+ split := strings.SplitN(cliVals.Platform, "/", 2)
+ cliVals.OverrideOS = split[0]
+ if len(split) > 1 {
+ cliVals.OverrideArch = split[1]
+ }
+ if pullPolicy != config.PullImageAlways {
+ logrus.Info("--platform causes the pull policy to be \"always\"")
+ pullPolicy = config.PullImageAlways
+ }
+ }
+
if imageMissing || pullPolicy == config.PullImageAlways {
if pullPolicy == config.PullImageNever {
return "", errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", imageName)
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go
index bcb31e6ee..8a7951923 100644
--- a/cmd/podman/images/list.go
+++ b/cmd/podman/images/list.go
@@ -126,8 +126,8 @@ func images(cmd *cobra.Command, args []string) error {
case listFlag.quiet:
return writeID(imgs)
default:
- if cmd.Flag("format").Changed {
- listFlag.noHeading = true // V1 compatibility
+ if cmd.Flags().Changed("format") && !parse.HasTable(listFlag.format) {
+ listFlag.noHeading = true
}
return writeTemplate(imgs)
}
diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go
index a6f41688c..f8e1ee226 100644
--- a/cmd/podman/images/pull.go
+++ b/cmd/podman/images/pull.go
@@ -3,6 +3,7 @@ package images
import (
"fmt"
"os"
+ "strings"
"github.com/containers/common/pkg/auth"
"github.com/containers/common/pkg/completion"
@@ -11,6 +12,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -94,6 +96,10 @@ func pullFlags(cmd *cobra.Command) {
flags.StringVar(&pullOptions.OverrideVariant, overrideVariantFlagName, "", " use VARIANT instead of the running architecture variant for choosing images")
_ = cmd.RegisterFlagCompletionFunc(overrideVariantFlagName, completion.AutocompleteNone)
+ platformFlagName := "platform"
+ flags.String(platformFlagName, "", "Specify the platform for selecting the image. (Conflicts with override-arch and override-os)")
+ _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone)
+
flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP")
flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images")
flags.StringVar(&pullOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
@@ -127,6 +133,20 @@ func imagePull(cmd *cobra.Command, args []string) error {
return err
}
}
+ platform, err := cmd.Flags().GetString("platform")
+ if err != nil {
+ return err
+ }
+ if platform != "" {
+ if pullOptions.OverrideArch != "" || pullOptions.OverrideOS != "" {
+ return errors.Errorf("--platform option can not be specified with --overide-arch or --override-os")
+ }
+ split := strings.SplitN(platform, "/", 2)
+ pullOptions.OverrideOS = split[0]
+ if len(split) > 1 {
+ pullOptions.OverrideArch = split[1]
+ }
+ }
if pullOptions.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(pullOptions.CredentialsCLI)
diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go
index 17de2c95d..8db4bb89a 100644
--- a/cmd/podman/networks/create.go
+++ b/cmd/podman/networks/create.go
@@ -6,9 +6,11 @@ import (
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v2/cmd/podman/common"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -27,6 +29,8 @@ var (
var (
networkCreateOptions entities.NetworkCreateOptions
+ labels []string
+ opts []string
)
func networkCreateFlags(cmd *cobra.Command) {
@@ -36,6 +40,10 @@ func networkCreateFlags(cmd *cobra.Command) {
flags.StringVarP(&networkCreateOptions.Driver, driverFlagName, "d", "bridge", "driver to manage the network")
_ = cmd.RegisterFlagCompletionFunc(driverFlagName, common.AutocompleteNetworkDriver)
+ optFlagName := "opt"
+ flags.StringArrayVarP(&opts, optFlagName, "o", []string{}, "Set driver specific options (default [])")
+ _ = cmd.RegisterFlagCompletionFunc(optFlagName, completion.AutocompleteNone)
+
gatewayFlagName := "gateway"
flags.IPVar(&networkCreateOptions.Gateway, gatewayFlagName, nil, "IPv4 or IPv6 gateway for the subnet")
_ = cmd.RegisterFlagCompletionFunc(gatewayFlagName, completion.AutocompleteNone)
@@ -50,6 +58,10 @@ func networkCreateFlags(cmd *cobra.Command) {
flags.StringVar(&networkCreateOptions.MacVLAN, macvlanFlagName, "", "create a Macvlan connection based on this device")
_ = cmd.RegisterFlagCompletionFunc(macvlanFlagName, completion.AutocompleteNone)
+ labelFlagName := "label"
+ flags.StringArrayVar(&labels, labelFlagName, nil, "set metadata on a network")
+ _ = cmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone)
+
// TODO not supported yet
// flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
@@ -81,6 +93,15 @@ func networkCreate(cmd *cobra.Command, args []string) error {
}
name = args[0]
}
+ var err error
+ networkCreateOptions.Labels, err = parse.GetAllLabels([]string{}, labels)
+ if err != nil {
+ return errors.Wrap(err, "failed to parse labels")
+ }
+ networkCreateOptions.Options, err = parse.GetAllLabels([]string{}, opts)
+ if err != nil {
+ return errors.Wrapf(err, "unable to process options")
+ }
response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions)
if err != nil {
return err
diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go
index dcba3f186..16ae980dc 100644
--- a/cmd/podman/networks/list.go
+++ b/cmd/podman/networks/list.go
@@ -16,6 +16,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -35,17 +36,20 @@ var (
var (
networkListOptions entities.NetworkListOptions
+ filters []string
+ noTrunc bool
)
func networkListFlags(flags *pflag.FlagSet) {
formatFlagName := "format"
- flags.StringVarP(&networkListOptions.Format, formatFlagName, "f", "", "Pretty-print networks to JSON or using a Go template")
+ flags.StringVar(&networkListOptions.Format, formatFlagName, "", "Pretty-print networks to JSON or using a Go template")
_ = networklistCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat)
flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names")
+ flags.BoolVar(&noTrunc, "no-trunc", false, "Do not truncate the network ID")
filterFlagName := "filter"
- flags.StringVarP(&networkListOptions.Filter, filterFlagName, "", "", "Provide filter values (e.g. 'name=podman')")
+ flags.StringArrayVarP(&filters, filterFlagName, "f", nil, "Provide filter values (e.g. 'name=podman')")
_ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteNetworkFilters)
}
@@ -61,14 +65,14 @@ func init() {
}
func networkList(cmd *cobra.Command, args []string) error {
- // validate the filter pattern.
- if len(networkListOptions.Filter) > 0 {
- tokens := strings.Split(networkListOptions.Filter, "=")
- if len(tokens) != 2 {
- return fmt.Errorf("invalid filter syntax : %s", networkListOptions.Filter)
+ networkListOptions.Filters = make(map[string][]string)
+ for _, f := range filters {
+ split := strings.SplitN(f, "=", 2)
+ if len(split) == 1 {
+ return errors.Errorf("invalid filter %q", f)
}
+ networkListOptions.Filters[split[0]] = append(networkListOptions.Filters[split[0]], split[1])
}
-
responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil {
return err
@@ -93,6 +97,8 @@ func networkList(cmd *cobra.Command, args []string) error {
"CNIVersion": "version",
"Version": "version",
"Plugins": "plugins",
+ "Labels": "labels",
+ "ID": "network id",
})
renderHeaders := true
row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n"
@@ -144,3 +150,19 @@ func (n ListPrintReports) Version() string {
func (n ListPrintReports) Plugins() string {
return network.GetCNIPlugins(n.NetworkConfigList)
}
+
+func (n ListPrintReports) Labels() string {
+ list := make([]string, 0, len(n.NetworkListReport.Labels))
+ for k, v := range n.NetworkListReport.Labels {
+ list = append(list, k+"="+v)
+ }
+ return strings.Join(list, ",")
+}
+
+func (n ListPrintReports) ID() string {
+ length := 12
+ if noTrunc {
+ length = 64
+ }
+ return network.GetNetworkID(n.Name)[:length]
+}
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 7840e6100..0830a62a5 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -71,7 +71,7 @@ var (
DisableFlagsInUseLine: true,
}
- logLevel = "error"
+ logLevel = "warn"
useSyslog bool
requireCleanup = true
)
diff --git a/docs/source/markdown/podman-cp.1.md b/docs/source/markdown/podman-cp.1.md
index 8f63c00ee..241a74c42 100644
--- a/docs/source/markdown/podman-cp.1.md
+++ b/docs/source/markdown/podman-cp.1.md
@@ -9,12 +9,12 @@ podman\-cp - Copy files/folders between a container and the local filesystem
**podman container cp** [*options*] [*container*:]*src_path* [*container*:]*dest_path*
## DESCRIPTION
-Copies the contents of **src_path** to the **dest_path**. You can copy from the container's filesystem to the local machine or the reverse, from the local filesystem to the container.
-If - is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.
+Copy the contents of **src_path** to the **dest_path**. You can copy from the container's filesystem to the local machine or the reverse, from the local filesystem to the container.
+If `-` is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.
The CONTAINER can be a running or stopped container. The **src_path** or **dest_path** can be a file or directory.
-The **podman cp** command assumes container paths are relative to the container's / (root) directory.
+The **podman cp** command assumes container paths are relative to the container's root directory (i.e., `/`).
This means supplying the initial forward slash is optional;
@@ -27,24 +27,22 @@ Assuming a path separator of /, a first argument of **src_path** and second argu
**src_path** specifies a file
- **dest_path** does not exist
- - the file is saved to a file created at **dest_path**
- - **dest_path** does not exist and ends with /
- - Error condition: the destination directory must exist.
+ - the file is saved to a file created at **dest_path** (note that parent directory must exist)
- **dest_path** exists and is a file
- - the destination is overwritten with the source file's contents
+ - the destination is overwritten with the source file's contents
- **dest_path** exists and is a directory
- - the file is copied into this directory using the basename from **src_path**
+ - the file is copied into this directory using the basename from **src_path**
**src_path** specifies a directory
- **dest_path** does not exist
- - **dest_path** is created as a directory and the contents of the source directory are copied into this directory
+ - **dest_path** is created as a directory and the contents of the source directory are copied into this directory
- **dest_path** exists and is a file
- - Error condition: cannot copy a directory to a file
+ - Error condition: cannot copy a directory to a file
- **dest_path** exists and is a directory
- - **src_path** ends with /
- - the source directory is copied into this directory
- - **src_path** ends with /. (that is: slash followed by dot)
- - the content of the source directory is copied into this directory
+ - **src_path** ends with `/`
+ - the source directory is copied into this directory
+ - **src_path** ends with `/.` (i.e., slash followed by dot)
+ - the content of the source directory is copied into this directory
The command requires **src_path** and **dest_path** to exist according to the above rules.
@@ -57,11 +55,13 @@ You can also use : when specifying paths to a **src_path** or **dest_path** on a
If you use a : in a local machine path, you must be explicit with a relative or absolute path, for example:
`/path/to/file:name.txt` or `./file:name.txt`
+Using `-` as the *src_path* streams the contents of STDIN as a tar archive. The command extracts the content of the tar to the *DEST_PATH* in the container. In this case, *dest_path* must specify a directory. Using `-` as the *dest_path* streams the contents of the resource (can be a directory) as a tar archive to STDOUT.
+
## OPTIONS
#### **--extract**
-Extract the tar file into the destination directory. If the destination directory is not provided, extract the tar file into the root directory.
+If the source is a tar archive, extract it to the provided destination (must be a directory). If the source is not a tar archive, follow the above rules.
#### **--pause**
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index ab390447e..843e2b22f 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -689,6 +689,11 @@ Default is to create a private PID namespace for the container
Tune the container's pids limit. Set `0` to have unlimited pids for the container. (default "4096" on systems that support PIDS cgroups).
+#### **--platform**=*OS/ARCH*
+
+Specify the platform for selecting the image. (Conflicts with override-arch and override-os)
+The `--platform` option can be used to override the current architecture and operating system.
+
#### **--pod**=*name*
Run container in an existing pod. If you want Podman to make the pod for you, preference the pod name with `new:`.
diff --git a/docs/source/markdown/podman-load.1.md b/docs/source/markdown/podman-load.1.md
index 177709a43..dc2a632e5 100644
--- a/docs/source/markdown/podman-load.1.md
+++ b/docs/source/markdown/podman-load.1.md
@@ -10,7 +10,7 @@ podman\-load - Load image(s) from a tar archive into container storage
## DESCRIPTION
**podman load** loads an image from either an **oci-archive** or a **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set.
-You can also specify a name for the image if the archive does not contain a named reference, of if you want an additional name for the local image.
+You can also specify a name for the image if the archive is of single image and load will tag an additional image with the name:tag.
**podman load** is used for loading from the archive generated by **podman save**, that includes the image parent layers. To load the archive of container's filesystem created by **podman export**, use **podman import**.
The local client further supports loading an **oci-dir** or a **docker-dir** as created with **podman save** (1).
diff --git a/docs/source/markdown/podman-network-connect.1.md b/docs/source/markdown/podman-network-connect.1.md
index 58b6e5c44..a31a415dc 100644
--- a/docs/source/markdown/podman-network-connect.1.md
+++ b/docs/source/markdown/podman-network-connect.1.md
@@ -10,6 +10,8 @@ podman\-network\-connect - Connect a container to a network
Connects a container to a network. A container can be connected to a network by name or by ID.
Once connected, the container can communicate with other containers in the same network.
+This command is not available for rootless users.
+
## OPTIONS
#### **--alias**
Add network-scoped alias for the container. If the network is using the `dnsname` CNI plugin, these aliases
diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md
index cbf9d26dc..16e4e3bdb 100644
--- a/docs/source/markdown/podman-network-create.1.md
+++ b/docs/source/markdown/podman-network-create.1.md
@@ -26,6 +26,14 @@ resolution.
Driver to manage the network (default "bridge"). Currently only `bridge` is supported.
+#### **--opt**=*option*, **-o**
+
+Set driver specific options.
+
+For the `bridge` driver the following options are supported: `mtu` and `vlan`.
+The `mtu` option sets the Maximum Transmission Unit (MTU) and takes an integer value.
+The `vlan` option assign VLAN tag and enables vlan\_filtering. Defaults to none.
+
#### **--gateway**
Define a gateway for the subnet. If you want to provide a gateway address, you must also provide a
@@ -40,6 +48,10 @@ Restrict external access of this network
Allocate container IP from a range. The range must be a complete subnet and in CIDR notation. The *ip-range* option
must be used with a *subnet* option.
+#### **--label**
+
+Set metadata for a network (e.g., --label mykey=value).
+
#### **--macvlan**
Create a *Macvlan* based connection rather than a classic bridge. You must pass an interface name from the host for the
diff --git a/docs/source/markdown/podman-network-disconnect.1.md b/docs/source/markdown/podman-network-disconnect.1.md
index 95c7018a8..8b7125282 100644
--- a/docs/source/markdown/podman-network-disconnect.1.md
+++ b/docs/source/markdown/podman-network-disconnect.1.md
@@ -9,6 +9,8 @@ podman\-network\-disconnect - Disconnect a container from a network
## DESCRIPTION
Disconnects a container from a network.
+This command is not available for rootless users.
+
## OPTIONS
#### **--force**, **-f**
diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md
index 47d647b3f..56515d0c1 100644
--- a/docs/source/markdown/podman-network-inspect.1.md
+++ b/docs/source/markdown/podman-network-inspect.1.md
@@ -7,7 +7,7 @@ podman\-network\-inspect - Displays the raw CNI network configuration for one or
**podman network inspect** [*options*] [*network* ...]
## DESCRIPTION
-Display the raw (JSON format) network configuration. This command is not available for rootless users.
+Display the raw (JSON format) network configuration.
## OPTIONS
#### **--format**, **-f**
diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md
index 34b40b3ae..9d2dd52a8 100644
--- a/docs/source/markdown/podman-network-ls.1.md
+++ b/docs/source/markdown/podman-network-ls.1.md
@@ -7,20 +7,47 @@ podman\-network\-ls - Display a summary of CNI networks
**podman network ls** [*options*]
## DESCRIPTION
-Displays a list of existing podman networks. This command is not available for rootless users.
+Displays a list of existing podman networks.
## OPTIONS
-#### **--quiet**, **-q**
+#### **--filter**, **-f**
-The `quiet` option will restrict the output to only the network names.
+Filter output based on conditions given.
+Multiple filters can be given with multiple uses of the --filter flag.
+Filters with the same key work inclusive with the only exception being
+`label` which is exclusive. Filters with different keys always work exclusive.
+
+Valid filters are listed below:
+
+| **Filter** | **Description** |
+| ---------- | ------------------------------------------------------------------------------------- |
+| name | [Name] Network name (accepts regex) |
+| id | [ID] Full or partial network ID |
+| label | [Key] or [Key=Value] Label assigned to a network |
+| plugin | [Plugin] CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`) |
+| driver | [Driver] Only `bridge` is supported |
+
+#### **--format**
-#### **--format**, **-f**
+Change the default output format. This can be of a supported type like 'json'
+or a Go template.
+Valid placeholders for the Go template are listed below:
-Pretty-print networks to JSON or using a Go template.
+| **Placeholder** | **Description** |
+| --------------- | --------------------------------|
+| .ID | Network ID |
+| .Name | Network name |
+| .Plugins | Network Plugins |
+| .Labels | Network labels |
+| .Version | CNI Version of the config file |
-#### **--filter**
+#### **--no-trunc**
-Provide filter values (e.g. 'name=podman').
+Do not truncate the network ID. The network ID is not displayed by default and must be specified with **--format**.
+
+#### **--quiet**, **-q**
+
+The `quiet` option will restrict the output to only the network names.
## EXAMPLE
diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md
index d21b200d9..bc161659a 100644
--- a/docs/source/markdown/podman-network.1.md
+++ b/docs/source/markdown/podman-network.1.md
@@ -7,7 +7,7 @@ podman\-network - Manage Podman CNI networks
**podman network** *subcommand*
## DESCRIPTION
-The network command manages CNI networks for Podman. It is not supported for rootless users.
+The network command manages CNI networks for Podman.
## COMMANDS
diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md
index 4495cec3a..44a7e83b6 100644
--- a/docs/source/markdown/podman-pull.1.md
+++ b/docs/source/markdown/podman-pull.1.md
@@ -106,6 +106,11 @@ Override the OS, defaults to hosts, of the image to be pulled. For example, `win
Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7.
+#### **--platform**=*OS/ARCH*
+
+Specify the platform for selecting the image. (Conflicts with override-arch and override-os)
+The `--platform` option can be used to override the current architecture and operating system.
+
#### **--quiet**, **-q**
Suppress output information when pulling images
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 13b586e17..53c5b2d4b 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -26,9 +26,12 @@ Several files will be automatically created within the container. These include
_/etc/hosts_, _/etc/hostname_, and _/etc/resolv.conf_ to manage networking.
These will be based on the host's version of the files, though they can be
customized with options (for example, **--dns** will override the host's DNS
-servers in the created _resolv.conf_). Additionally, an empty file is created in
-each container to indicate to programs they are running in a container. This file
-is located at _/run/.containerenv_.
+servers in the created _resolv.conf_). Additionally, a container environment
+file is created in each container to indicate to programs they are running in a
+container. This file is located at _/run/.containerenv_. When using the
+--privileged flag the .containerenv contains name/value pairs indicating the
+container engine version, whether the engine is running in rootless mode, the
+container name and id, as well as the image name and id that the container is based on.
When running from a user defined network namespace, the _/etc/netns/NSNAME/resolv.conf_
will be used if it exists, otherwise _/etc/resolv.conf_ will be used.
@@ -717,6 +720,11 @@ The efault is to create a private PID namespace for the container.
Tune the container's pids limit. Set to **0** to have unlimited pids for the container. The default is **4096** on systems that support "pids" cgroup controller.
+#### **--platform**=*OS/ARCH*
+
+Specify the platform for selecting the image. (Conflicts with override-arch and override-os)
+The `--platform` option can be used to override the current architecture and operating system.
+
#### **--pod**=*name*
Run container in an existing pod. If you want Podman to make the pod for you, prefix the pod name with **new:**.
@@ -880,11 +888,16 @@ Security Options
- **label=level:**_LEVEL_: Set the label level for the container processes
- **label=filetype:**TYPE_: Set the label file type for the container files
- **label=disable**: Turn off label separation for the container
+- **mask**=_/path/1:/path/2_: The paths to mask separated by a colon. A masked path
+ cannot be accessed inside the container.
- **no-new-privileges**: Disable container processes from gaining additional privileges
- **seccomp=unconfined**: Turn off seccomp confinement for the container
- **seccomp**=_profile.json_: Allowed syscall list seccomp JSON file to be used as a seccomp filter
- **proc-opts**=_OPTIONS_ : Comma separated list of options to use for the /proc mount. More details
for the possible mount options are specified at **proc(5)** man page.
+- **unmask**=_ALL_ or _/path/1:/path/2_: Paths to unmask separated by a colon. If set to **ALL**, it will
+ unmask all the paths that are masked by default.
+ The default masked paths are **/proc/acpi, /proc/kcore, /proc/keys, /proc/latency_stats, /proc/sched_debug, /proc/scsi, /proc/timer_list, /proc/timer_stats, /sys/firmware, and /sys/fs/selinux.**
Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file.
@@ -1474,6 +1487,26 @@ $ podman run --security-opt label=type:svirt_apache_t -i -t centos bash
Note you would have to write policy defining a **svirt_apache_t** type.
+To mask additional specific paths in the container, specify the paths
+separated by a colon using the **mask** option with the **--security-opt**
+flag.
+
+```
+$ podman run --security-opt mask=/foo/bar:/second/path fedora bash
+```
+
+To unmask all the paths that are masked by default, set the **unmask** option to
+**ALL**. Or to only unmask specific paths, specify the paths as shown above with
+the **mask** option.
+
+```
+$ podman run --security-opt unmask=ALL fedora bash
+```
+
+```
+$ podman run --security-opt unmask=/foo/bar:/sys/firmware fedora bash
+```
+
### Setting device weight
If you want to set _/dev/sda_ device weight to **200**, you can specify the device
diff --git a/go.mod b/go.mod
index ec2d643e8..f822e7994 100644
--- a/go.mod
+++ b/go.mod
@@ -11,18 +11,19 @@ require (
github.com/containernetworking/cni v0.8.0
github.com/containernetworking/plugins v0.8.7
github.com/containers/buildah v1.18.1-0.20201125084616-dd26b137459c
- github.com/containers/common v0.29.0
+ github.com/containers/common v0.31.0
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.8.1
github.com/containers/psgo v1.5.1
github.com/containers/storage v1.24.1
github.com/coreos/go-systemd/v22 v22.1.0
- github.com/cri-o/ocicni v0.2.1-0.20201102180012-75c612fda1a2
+ github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c
github.com/cyphar/filepath-securejoin v0.2.2
github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible
github.com/docker/go-connections v0.4.0
+ github.com/docker/go-plugins-helpers v0.0.0-20200102110956-c9a8a2d92ccc
github.com/docker/go-units v0.4.0
github.com/fsnotify/fsnotify v1.4.9
github.com/ghodss/yaml v1.0.0
@@ -72,7 +73,3 @@ require (
k8s.io/apimachinery v0.19.4
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab
)
-
-replace github.com/cri-o/ocicni => github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df
-
-replace github.com/spf13/cobra => github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706
diff --git a/go.sum b/go.sum
index b0626def1..111e9f7d2 100644
--- a/go.sum
+++ b/go.sum
@@ -20,8 +20,6 @@ github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706 h1:KcMtguD/NlxB4c08lzc91o5by51Sf+Ec5+1Yv9Wqvbk=
-github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/go-winio v0.4.15-0.20200113171025-3fe6c5262873 h1:93nQ7k53GjoMQ07HVP8g6Zj1fQZDDj7Xy2VkNNtvX8o=
@@ -97,6 +95,8 @@ github.com/containers/buildah v1.18.1-0.20201125084616-dd26b137459c h1:vyc2iYz9b
github.com/containers/buildah v1.18.1-0.20201125084616-dd26b137459c/go.mod h1:B+0OkXUogxdwsEy4ax3a5/vDtJjL6vCisiV6frQZJ4A=
github.com/containers/common v0.29.0 h1:hTMC+urdkk5bKfhL/OgCixIX5xjJgQ2l2jPG745ECFQ=
github.com/containers/common v0.29.0/go.mod h1:yT4GTUHsKRmpaDb+mecXRnIMre7W3ZgwXqaYMywXlaA=
+github.com/containers/common v0.31.0 h1:SRnjfoqbjfaojpY9YJq9JBPEslwB5hoXJbaE+5zMFwM=
+github.com/containers/common v0.31.0/go.mod h1:yT4GTUHsKRmpaDb+mecXRnIMre7W3ZgwXqaYMywXlaA=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.8.1 h1:aHW8a/Kd0dTJ7PTL/fc6y12sJqHxWgqilu+XyHfjD8Q=
@@ -126,8 +126,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df h1:c35uRFkER07nAkB1X21e+PI5xO21SOyI6G7tdfvz1z4=
-github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df/go.mod h1:vingr1ztOAzP2WyTgGbpMov9dFhbjNxdLtDv0+PhAvY=
+github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c h1:iGaCU6d3oVT0pl8tmvyDhoA/vTDL3IX08akfsKZIy9o=
+github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c/go.mod h1:vingr1ztOAzP2WyTgGbpMov9dFhbjNxdLtDv0+PhAvY=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
@@ -152,6 +152,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
+github.com/docker/go-plugins-helpers v0.0.0-20200102110956-c9a8a2d92ccc h1:/A+mPcpajLsWiX9gSnzdVKM/IzZoYiNqXHe83z50k2c=
+github.com/docker/go-plugins-helpers v0.0.0-20200102110956-c9a8a2d92ccc/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316 h1:moehPjPiGUaWdwgOl92xRyFHJyaqXDHcCyW9M6nmCK4=
@@ -500,6 +502,9 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
diff --git a/hack/podman-socat b/hack/podman-socat
new file mode 100755
index 000000000..7bc571816
--- /dev/null
+++ b/hack/podman-socat
@@ -0,0 +1,122 @@
+#!/bin/bash -e
+# Execute podman while capturing the API stream
+#
+# Script will run an instance of podman sand-boxed, the API stream will be captured and then formatted for readability.
+
+if [[ $(id -u) != 0 ]]; then
+ echo >&2 "$0 must be run as root."
+ exit 2
+fi
+
+if ! command -v socat >/dev/null 2>&1; then
+ echo 1>&2 "socat not found on PATH"
+fi
+
+PODMAN=${PODMAN:-podman}
+if ! command -v "$PODMAN" >/dev/null 2>&1; then
+ echo 1>&2 "$PODMAN not found on PATH"
+fi
+
+function usage() {
+ echo 1>&2 $0 '[-v] [-h]'
+}
+
+while getopts "vh" arg; do
+ case $arg in
+ v)
+ VERBOSE='-v'
+ export PODMAN_LOG_LEVEL=debug
+ ;;
+ h)
+ usage
+ exit 0
+ ;;
+ \?)
+ usage
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+function cleanup() {
+ set +xeuo pipefail
+ rm -r "$1"
+ kill -9 $REAP_PIDS
+
+ sed -e 's/^> /\nClient Request> /' -e 's/^< /\nServer Response< /' -i /tmp/podman-socat.log
+}
+
+# Create temporary directory for storage
+export TMPDIR=$(mktemp -d /tmp/podman.XXXXXXXXXX)
+trap "cleanup $TMPDIR" EXIT
+
+# Need locations to store stuff
+mkdir -p "${TMPDIR}"/{podman,crio,crio-run,cni/net.d,ctnr,tunnel}
+
+export REGISTRIES_CONFIG_PATH=${TMPDIR}/registry.conf
+cat >"$REGISTRIES_CONFIG_PATH" <<-EOT
+ [registries.search]
+ registries = ['docker.io']
+ [registries.insecure]
+ registries = []
+ [registries.block]
+ registries = []
+EOT
+
+export CNI_CONFIG_PATH=${TMPDIR}/cni/net.d
+cat >"$CNI_CONFIG_PATH"/87-podman-bridge.conflist <<-EOT
+{
+ "cniVersion": "0.3.0",
+ "name": "podman",
+ "plugins": [{
+ "type": "bridge",
+ "bridge": "cni0",
+ "isGateway": true,
+ "ipMasq": true,
+ "ipam": {
+ "type": "host-local",
+ "subnet": "10.88.0.0/16",
+ "routes": [{
+ "dst": "0.0.0.0/0"
+ }]
+ }
+ },
+ {
+ "type": "portmap",
+ "capabilities": {
+ "portMappings": true
+ }
+ }
+ ]
+}
+EOT
+
+PODMAN_ARGS="--storage-driver=vfs \
+ --root=${TMPDIR}/crio \
+ --runroot=${TMPDIR}/crio-run \
+ --cni-config-dir=$CNI_CONFIG_PATH \
+ --cgroup-manager=systemd \
+ "
+if [[ -n $VERBOSE ]]; then
+ PODMAN_ARGS="$PODMAN_ARGS --log-level=$PODMAN_LOG_LEVEL --syslog=true"
+fi
+PODMAN="$PODMAN $PODMAN_ARGS"
+
+PODMAN_HOST="${TMPDIR}/podman/podman-socat.sock"
+SOCAT_HOST="${TMPDIR}/podman/podman.sock"
+
+cat <<-EOT
+Podman service running at unix:$SOCAT_HOST
+See /tmp/podman-socat.log for API stream capture
+See /tmp/podman-service.log for service logging
+
+usage: sudo bin/podman-remote --url unix:$SOCAT_HOST images
+
+^C to exit
+EOT
+
+$PODMAN system service --timeout=0 "unix:$PODMAN_HOST" >/tmp/podman-service.log 2>&1 &
+REAP_PIDS=$!
+
+socat -v "UNIX-LISTEN:$SOCAT_HOST",fork,reuseaddr,unlink-early "UNIX-CONNECT:$PODMAN_HOST" >/tmp/podman-socat.log 2>&1
diff --git a/install.md b/install.md
index 2ef6eae2c..d09abec3a 100644
--- a/install.md
+++ b/install.md
@@ -1,5 +1,5 @@
# libpod Installation Instructions
-The installation instructions for Podman and libpod now reside **[here](https://podman.io/getting-started/installation)** in the **[podman.io](https://podman.io)** site. From the homepage, the installation instructions can be found under "Get Started->Installing Podman".
+The installation instructions for Podman and libpod now reside **[here](https://podman.io/getting-started/installation)** on the **[podman.io](https://podman.io)** site.
-The podman.io site resides in a GitHub under the Containers repository at [https://github.com/containers/podman.io](https://github.com/containers/podman.io). If you see a change that needs to happen to the installation instructions, please feel free to open a pull request there, we're always happy to have new contributors!
+The podman.io site resides in a GitHub repository under the containers organization at [https://github.com/containers/podman.io](https://github.com/containers/podman.io). If you see a change that needs to happen to the installation instructions, please feel free to open a pull request there. We're always happy to have new contributors!
diff --git a/libpod/container.go b/libpod/container.go
index e954d84eb..4e0687318 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -267,6 +267,11 @@ func (c *Container) Config() *ContainerConfig {
return returnConfig
}
+// Runtime returns the container's Runtime.
+func (c *Container) Runtime() *Runtime {
+ return c.runtime
+}
+
// Spec returns the container's OCI runtime spec
// The spec returned is the one used to create the container. The running
// spec may differ slightly as mounts are added based on the image
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index f78d74ef7..2ce3e8e68 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -488,7 +488,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
// Weight? For now, ignore anything
// without Weight set.
if dev.Weight == nil {
- logrus.Warnf("Ignoring weight device %s as it lacks a weight", key)
+ logrus.Infof("Ignoring weight device %s as it lacks a weight", key)
continue
}
if deviceNodes == nil {
@@ -500,7 +500,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
}
path, ok := deviceNodes[key]
if !ok {
- logrus.Warnf("Could not locate weight device %s in system devices", key)
+ logrus.Infof("Could not locate weight device %s in system devices", key)
continue
}
weightDev := define.InspectBlkioWeightDevice{}
@@ -522,7 +522,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
}
path, ok := deviceNodes[key]
if !ok {
- logrus.Warnf("Could not locate throttle device %s in system devices", key)
+ logrus.Infof("Could not locate throttle device %s in system devices", key)
continue
}
throttleDev := define.InspectBlkioThrottleDevice{}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index b6a3244ea..c751d775d 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -525,7 +525,7 @@ func (c *Container) teardownStorage() error {
// Potentially another tool using containers/storage already
// removed it?
if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown {
- logrus.Warnf("Storage for container %s already removed", c.ID())
+ logrus.Infof("Storage for container %s already removed", c.ID())
return nil
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 56575c195..72eaeac8e 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -35,6 +35,7 @@ import (
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
"github.com/containers/podman/v2/utils"
+ "github.com/containers/podman/v2/version"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
securejoin "github.com/cyphar/filepath-securejoin"
@@ -352,7 +353,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
if !MountExists(g.Mounts(), dstPath) {
g.AddMount(newMount)
} else {
- logrus.Warnf("User mount overriding libpod mount at %q", dstPath)
+ logrus.Infof("User mount overriding libpod mount at %q", dstPath)
}
}
@@ -1423,11 +1424,26 @@ func (c *Container) makeBindMounts() error {
}
}
- // Make .containerenv
- // Empty file, so no need to recreate if it exists
+ // Make .containerenv if it does not exist
if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
- // Empty string for now, but we may consider populating this later
- containerenvPath, err := c.writeStringToRundir(".containerenv", "")
+ var containerenv string
+ isRootless := 0
+ if rootless.IsRootless() {
+ isRootless = 1
+ }
+ imageID, imageName := c.Image()
+
+ if c.Privileged() {
+ // Populate the .containerenv with container information
+ containerenv = fmt.Sprintf(`engine="podman-%s"
+name=%q
+id=%q
+image=%q
+imageid=%q
+rootless=%d
+`, version.Version.String(), c.Name(), c.ID(), imageName, imageID, isRootless)
+ }
+ containerenvPath, err := c.writeStringToRundir(".containerenv", containerenv)
if err != nil {
return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID())
}
diff --git a/libpod/events/config.go b/libpod/events/config.go
index fc1457289..085fa9d52 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -121,6 +121,8 @@ const (
Cleanup Status = "cleanup"
// Commit ...
Commit Status = "commit"
+ // Copy ...
+ Copy Status = "copy"
// Create ...
Create Status = "create"
// Exec ...
diff --git a/libpod/image/image.go b/libpod/image/image.go
index cecd64eb7..5c3f3b9e4 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -1222,6 +1222,11 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image
}
}
+ parent, err := i.ParentID(ctx)
+ if err != nil {
+ return nil, err
+ }
+
repoTags, err := i.RepoTags()
if err != nil {
return nil, err
@@ -1248,6 +1253,7 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image
data := &inspect.ImageData{
ID: i.ID(),
+ Parent: parent,
RepoTags: repoTags,
RepoDigests: repoDigests,
Comment: comment,
@@ -1258,10 +1264,12 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image
Config: &ociv1Img.Config,
Version: info.DockerVersion,
Size: size,
- VirtualSize: size,
- Annotations: annotations,
- Digest: i.Digest(),
- Labels: info.Labels,
+ // This is good enough for now, but has to be
+ // replaced later with correct calculation logic
+ VirtualSize: size,
+ Annotations: annotations,
+ Digest: i.Digest(),
+ Labels: info.Labels,
RootFS: &inspect.RootFS{
Type: ociv1Img.RootFS.Type,
Layers: ociv1Img.RootFS.DiffIDs,
@@ -1505,6 +1513,15 @@ func (i *Image) GetParent(ctx context.Context) (*Image, error) {
return tree.parent(ctx, i)
}
+// ParentID returns the image ID of the parent. Return empty string if a parent is not found.
+func (i *Image) ParentID(ctx context.Context) (string, error) {
+ parent, err := i.GetParent(ctx)
+ if err == nil && parent != nil {
+ return parent.ID(), nil
+ }
+ return "", err
+}
+
// GetChildren returns a list of the imageIDs that depend on the image
func (i *Image) GetChildren(ctx context.Context) ([]string, error) {
children, err := i.getChildren(ctx, true)
diff --git a/libpod/info.go b/libpod/info.go
index dd7a521c1..2f64a107e 100644
--- a/libpod/info.go
+++ b/libpod/info.go
@@ -117,7 +117,6 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
if rootless.IsRootless() {
if path, err := exec.LookPath("slirp4netns"); err == nil {
- logrus.Warnf("Failed to retrieve program version for %s: %v", path, err)
version, err := programVersion(path)
if err != nil {
logrus.Warnf("Failed to retrieve program version for %s: %v", path, err)
diff --git a/libpod/network/create.go b/libpod/network/create.go
index 7e4fc574a..094fbe349 100644
--- a/libpod/network/create.go
+++ b/libpod/network/create.go
@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "strconv"
"github.com/containernetworking/cni/pkg/version"
"github.com/containers/common/pkg/config"
@@ -76,6 +77,29 @@ func validateBridgeOptions(options entities.NetworkCreateOptions) error {
}
+// parseMTU parses the mtu option
+func parseMTU(mtu string) (int, error) {
+ if mtu == "" {
+ return 0, nil // default
+ }
+ m, err := strconv.Atoi(mtu)
+ if err != nil {
+ return 0, err
+ }
+ if m < 0 {
+ return 0, errors.Errorf("the value %d for mtu is less than zero", m)
+ }
+ return m, nil
+}
+
+// parseVlan parses the vlan option
+func parseVlan(vlan string) (int, error) {
+ if vlan == "" {
+ return 0, nil // default
+ }
+ return strconv.Atoi(vlan)
+}
+
// createBridge creates a CNI network
func createBridge(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
var (
@@ -149,6 +173,28 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon
ipMasq = false
}
+ var mtu int
+ var vlan int
+ for k, v := range options.Options {
+ var err error
+ switch k {
+ case "mtu":
+ mtu, err = parseMTU(v)
+ if err != nil {
+ return "", err
+ }
+
+ case "vlan":
+ vlan, err = parseVlan(v)
+ if err != nil {
+ return "", err
+ }
+
+ default:
+ return "", errors.Errorf("unsupported option %s", k)
+ }
+ }
+
// obtain host bridge name
bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig)
if err != nil {
@@ -169,10 +215,10 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon
}
// create CNI plugin configuration
- ncList := NewNcList(name, version.Current())
+ ncList := NewNcList(name, version.Current(), options.Labels)
var plugins []CNIPlugins
// TODO need to iron out the role of isDefaultGW and IPMasq
- bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
+ bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, mtu, vlan, ipamConfig)
plugins = append(plugins, bridge)
plugins = append(plugins, NewPortMapPlugin())
plugins = append(plugins, NewFirewallPlugin())
@@ -223,7 +269,7 @@ func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeCo
return "", err
}
}
- ncList := NewNcList(name, version.Current())
+ ncList := NewNcList(name, version.Current(), options.Labels)
macvlan := NewMacVLANPlugin(options.MacVLAN)
plugins = append(plugins, macvlan)
ncList["plugins"] = plugins
diff --git a/libpod/network/files.go b/libpod/network/files.go
index 7f1e3ee18..33cf01064 100644
--- a/libpod/network/files.go
+++ b/libpod/network/files.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/libpod/define"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// ErrNoSuchNetworkInterface indicates that no network interface exists
@@ -49,13 +50,15 @@ func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
return configs, nil
}
-// GetCNIConfigPathByName finds a CNI network by name and
+// GetCNIConfigPathByNameOrID finds a CNI network by name and
// returns its configuration file path
-func GetCNIConfigPathByName(config *config.Config, name string) (string, error) {
+func GetCNIConfigPathByNameOrID(config *config.Config, name string) (string, error) {
files, err := libcni.ConfFiles(GetCNIConfDir(config), []string{".conflist"})
if err != nil {
return "", err
}
+ idMatch := 0
+ file := ""
for _, confFile := range files {
conf, err := libcni.ConfListFromFile(confFile)
if err != nil {
@@ -64,6 +67,16 @@ func GetCNIConfigPathByName(config *config.Config, name string) (string, error)
if conf.Name == name {
return confFile, nil
}
+ if strings.HasPrefix(GetNetworkID(conf.Name), name) {
+ idMatch++
+ file = confFile
+ }
+ }
+ if idMatch == 1 {
+ return file, nil
+ }
+ if idMatch > 1 {
+ return "", errors.Errorf("more than one result for network ID %s", name)
}
return "", errors.Wrap(define.ErrNoSuchNetwork, fmt.Sprintf("unable to find network configuration for %s", name))
}
@@ -71,7 +84,7 @@ func GetCNIConfigPathByName(config *config.Config, name string) (string, error)
// ReadRawCNIConfByName reads the raw CNI configuration for a CNI
// network by name
func ReadRawCNIConfByName(config *config.Config, name string) ([]byte, error) {
- confFile, err := GetCNIConfigPathByName(config, name)
+ confFile, err := GetCNIConfigPathByNameOrID(config, name)
if err != nil {
return nil, err
}
@@ -89,6 +102,35 @@ func GetCNIPlugins(list *libcni.NetworkConfigList) string {
return strings.Join(plugins, ",")
}
+// GetNetworkLabels returns a list of labels as a string
+func GetNetworkLabels(list *libcni.NetworkConfigList) NcLabels {
+ cniJSON := make(map[string]interface{})
+ err := json.Unmarshal(list.Bytes, &cniJSON)
+ if err != nil {
+ logrus.Errorf("failed to unmarshal network config %v %v", cniJSON["name"], err)
+ return nil
+ }
+ if args, ok := cniJSON["args"]; ok {
+ if key, ok := args.(map[string]interface{}); ok {
+ if labels, ok := key[PodmanLabelKey]; ok {
+ if labels, ok := labels.(map[string]interface{}); ok {
+ result := make(NcLabels, len(labels))
+ for k, v := range labels {
+ if v, ok := v.(string); ok {
+ result[k] = v
+ } else {
+ logrus.Errorf("network config %v invalid label value type %T should be string", cniJSON["name"], labels)
+ }
+ }
+ return result
+ }
+ logrus.Errorf("network config %v invalid label type %T should be map[string]string", cniJSON["name"], labels)
+ }
+ }
+ }
+ return nil
+}
+
// GetNetworksFromFilesystem gets all the networks from the cni configuration
// files
func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) {
diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go
index ee9adce14..d61b96ecb 100644
--- a/libpod/network/netconflist.go
+++ b/libpod/network/netconflist.go
@@ -4,6 +4,11 @@ import (
"net"
"os"
"path/filepath"
+ "strings"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
)
const (
@@ -14,22 +19,36 @@ const (
// NcList describes a generic map
type NcList map[string]interface{}
+// NcArgs describes the cni args field
+type NcArgs map[string]NcLabels
+
+// NcLabels describes the label map
+type NcLabels map[string]string
+
+// PodmanLabelKey key used to store the podman network label in a cni config
+const PodmanLabelKey = "podman_labels"
+
// NewNcList creates a generic map of values with string
// keys and adds in version and network name
-func NewNcList(name, version string) NcList {
+func NewNcList(name, version string, labels NcLabels) NcList {
n := NcList{}
n["cniVersion"] = version
n["name"] = name
+ if len(labels) > 0 {
+ n["args"] = NcArgs{PodmanLabelKey: labels}
+ }
return n
}
// NewHostLocalBridge creates a new LocalBridge for host-local
-func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamConf IPAMHostLocalConf) *HostLocalBridge {
+func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, mtu int, vlan int, ipamConf IPAMHostLocalConf) *HostLocalBridge {
hostLocalBridge := HostLocalBridge{
PluginType: "bridge",
BrName: name,
IPMasq: ipMasq,
+ MTU: mtu,
HairpinMode: true,
+ Vlan: vlan,
IPAM: ipamConf,
}
if isGateWay {
@@ -159,3 +178,72 @@ func NewMacVLANPlugin(device string) MacVLANConfig {
}
return m
}
+
+// IfPassesFilter filters NetworkListReport and returns true if the filter match the given config
+func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]string) (bool, error) {
+ result := true
+ for key, filterValues := range filters {
+ result = false
+ switch strings.ToLower(key) {
+ case "name":
+ // matches one name, regex allowed
+ result = util.StringMatchRegexSlice(netconf.Name, filterValues)
+
+ case "plugin":
+ // match one plugin
+ plugins := GetCNIPlugins(netconf)
+ for _, val := range filterValues {
+ if strings.Contains(plugins, val) {
+ result = true
+ break
+ }
+ }
+
+ case "label":
+ // matches all labels
+ labels := GetNetworkLabels(netconf)
+ outer:
+ for _, filterValue := range filterValues {
+ filterArray := strings.SplitN(filterValue, "=", 2)
+ filterKey := filterArray[0]
+ if len(filterArray) > 1 {
+ filterValue = filterArray[1]
+ } else {
+ filterValue = ""
+ }
+ for labelKey, labelValue := range labels {
+ if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
+ result = true
+ continue outer
+ }
+ }
+ result = false
+ }
+
+ case "driver":
+ // matches only for the DefaultNetworkDriver
+ for _, filterValue := range filterValues {
+ plugins := GetCNIPlugins(netconf)
+ if filterValue == DefaultNetworkDriver &&
+ strings.Contains(plugins, DefaultNetworkDriver) {
+ result = true
+ }
+ }
+
+ case "id":
+ // matches part of one id
+ for _, filterValue := range filterValues {
+ if strings.Contains(GetNetworkID(netconf.Name), filterValue) {
+ result = true
+ break
+ }
+ }
+
+ // TODO: add dangling filter
+
+ default:
+ return false, errors.Errorf("invalid filter %q", key)
+ }
+ }
+ return result, nil
+}
diff --git a/libpod/network/network.go b/libpod/network/network.go
index 0febb52f6..89f0b67ac 100644
--- a/libpod/network/network.go
+++ b/libpod/network/network.go
@@ -1,6 +1,8 @@
package network
import (
+ "crypto/sha256"
+ "encoding/hex"
"encoding/json"
"net"
"os"
@@ -175,7 +177,7 @@ func RemoveNetwork(config *config.Config, name string) error {
return err
}
defer l.releaseCNILock()
- cniPath, err := GetCNIConfigPathByName(config, name)
+ cniPath, err := GetCNIConfigPathByNameOrID(config, name)
if err != nil {
return err
}
@@ -229,3 +231,10 @@ func Exists(config *config.Config, name string) (bool, error) {
}
return true, nil
}
+
+// GetNetworkID return the network ID for a given name.
+// It is just the sha256 hash but this should be good enough.
+func GetNetworkID(name string) string {
+ hash := sha256.Sum256([]byte(name))
+ return hex.EncodeToString(hash[:])
+}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 15e470c80..463378af7 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -245,7 +245,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
// setupSlirp4netns can be called in rootful as well as in rootless
func (r *Runtime) setupSlirp4netns(ctr *Container) error {
path := r.config.Engine.NetworkCmdPath
-
+ slirpOptions := r.config.Engine.NetworkCmdOptions
if path == "" {
var err error
path, err = exec.LookPath("slirp4netns")
@@ -273,68 +273,69 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error {
outboundAddr6 := ""
if ctr.config.NetworkOptions != nil {
- slirpOptions := ctr.config.NetworkOptions["slirp4netns"]
- for _, o := range slirpOptions {
- parts := strings.SplitN(o, "=", 2)
- if len(parts) < 2 {
- return errors.Errorf("unknown option for slirp4netns: %q", o)
+ slirpOptions = append(slirpOptions, ctr.config.NetworkOptions["slirp4netns"]...)
+ }
+
+ for _, o := range slirpOptions {
+ parts := strings.SplitN(o, "=", 2)
+ if len(parts) < 2 {
+ return errors.Errorf("unknown option for slirp4netns: %q", o)
+ }
+ option, value := parts[0], parts[1]
+ switch option {
+ case "cidr":
+ ipv4, _, err := net.ParseCIDR(value)
+ if err != nil || ipv4.To4() == nil {
+ return errors.Errorf("invalid cidr %q", value)
}
- option, value := parts[0], parts[1]
- switch option {
- case "cidr":
- ipv4, _, err := net.ParseCIDR(value)
- if err != nil || ipv4.To4() == nil {
- return errors.Errorf("invalid cidr %q", value)
- }
- cidr = value
- case "port_handler":
- switch value {
- case "slirp4netns":
- isSlirpHostForward = true
- case "rootlesskit":
- isSlirpHostForward = false
- default:
- return errors.Errorf("unknown port_handler for slirp4netns: %q", value)
- }
- case "allow_host_loopback":
- switch value {
- case "true":
- disableHostLoopback = false
- case "false":
- disableHostLoopback = true
- default:
- return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value)
- }
- case "enable_ipv6":
- switch value {
- case "true":
- enableIPv6 = true
- case "false":
- enableIPv6 = false
- default:
- return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value)
- }
- case "outbound_addr":
- ipv4 := net.ParseIP(value)
- if ipv4 == nil || ipv4.To4() == nil {
- _, err := net.InterfaceByName(value)
- if err != nil {
- return errors.Errorf("invalid outbound_addr %q", value)
- }
+ cidr = value
+ case "port_handler":
+ switch value {
+ case "slirp4netns":
+ isSlirpHostForward = true
+ case "rootlesskit":
+ isSlirpHostForward = false
+ default:
+ return errors.Errorf("unknown port_handler for slirp4netns: %q", value)
+ }
+ case "allow_host_loopback":
+ switch value {
+ case "true":
+ disableHostLoopback = false
+ case "false":
+ disableHostLoopback = true
+ default:
+ return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value)
+ }
+ case "enable_ipv6":
+ switch value {
+ case "true":
+ enableIPv6 = true
+ case "false":
+ enableIPv6 = false
+ default:
+ return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value)
+ }
+ case "outbound_addr":
+ ipv4 := net.ParseIP(value)
+ if ipv4 == nil || ipv4.To4() == nil {
+ _, err := net.InterfaceByName(value)
+ if err != nil {
+ return errors.Errorf("invalid outbound_addr %q", value)
}
- outboundAddr = value
- case "outbound_addr6":
- ipv6 := net.ParseIP(value)
- if ipv6 == nil || ipv6.To4() != nil {
- _, err := net.InterfaceByName(value)
- if err != nil {
- return errors.Errorf("invalid outbound_addr6: %q", value)
- }
+ }
+ outboundAddr = value
+ case "outbound_addr6":
+ ipv6 := net.ParseIP(value)
+ if ipv6 == nil || ipv6.To4() != nil {
+ _, err := net.InterfaceByName(value)
+ if err != nil {
+ return errors.Errorf("invalid outbound_addr6: %q", value)
}
- outboundAddr6 = value
- default:
- return errors.Errorf("unknown option for slirp4netns: %q", o)
}
+ outboundAddr6 = value
+ default:
+ return errors.Errorf("unknown option for slirp4netns: %q", o)
}
}
diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go
index 7068bf87a..f8e7020f7 100644
--- a/libpod/oci_conmon_exec_linux.go
+++ b/libpod/oci_conmon_exec_linux.go
@@ -231,7 +231,7 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t
// Wait for the PID to stop
if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil {
- logrus.Warnf("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL", ctr.ID(), sessionID)
+ logrus.Infof("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL: %v", ctr.ID(), sessionID, err)
} else {
// No error, container is dead
return nil
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index bd58610a2..307b9bc54 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -442,7 +442,7 @@ func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool)
}
if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil {
- logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID())
+ logrus.Infof("Timed out stopping container %s, resorting to SIGKILL: %v", ctr.ID(), err)
} else {
// No error, the container is dead
return nil
@@ -1009,7 +1009,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if ctr.config.SdNotifyMode == define.SdNotifyModeIgnore {
if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
- logrus.Warnf("Error unsetting NOTIFY_SOCKET %s", err.Error())
+ logrus.Warnf("Error unsetting NOTIFY_SOCKET %v", err)
}
}
@@ -1155,14 +1155,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile)
if err != nil {
- logrus.Warnf("error reading conmon pid file for container %s: %s", ctr.ID(), err.Error())
+ logrus.Warnf("error reading conmon pid file for container %s: %v", ctr.ID(), err)
} else if conmonPID > 0 {
// conmon not having a pid file is a valid state, so don't set it if we don't have it
logrus.Infof("Got Conmon PID as %d", conmonPID)
ctr.state.ConmonPID = conmonPID
if ctr.config.SdNotifyMode != define.SdNotifyModeIgnore {
if sent, err := daemon.SdNotify(false, fmt.Sprintf("MAINPID=%d", conmonPID)); err != nil {
- logrus.Errorf("Error notifying systemd of Conmon PID: %s", err.Error())
+ logrus.Errorf("Error notifying systemd of Conmon PID: %v", err)
} else if sent {
logrus.Debugf("Notify MAINPID sent successfully")
}
diff --git a/libpod/options.go b/libpod/options.go
index 0f55f34a3..bd12c0c34 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1547,12 +1547,19 @@ func WithVolumeDriver(driver string) VolumeCreateOption {
if volume.valid {
return define.ErrVolumeFinalized
}
- // only local driver is possible rn
+
+ // Uncomment when volume plugins are ready for use.
+ // if driver != define.VolumeDriverLocal {
+ // if _, err := plugin.GetVolumePlugin(driver); err != nil {
+ // return err
+ // }
+ // }
+
if driver != define.VolumeDriverLocal {
return define.ErrNotImplemented
-
}
- volume.config.Driver = define.VolumeDriverLocal
+
+ volume.config.Driver = driver
return nil
}
}
diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go
new file mode 100644
index 000000000..2500a4f36
--- /dev/null
+++ b/libpod/plugin/volume_api.go
@@ -0,0 +1,454 @@
+package plugin
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/docker/go-plugins-helpers/sdk"
+ "github.com/docker/go-plugins-helpers/volume"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+// TODO: We should add syntax for specifying plugins to containers.conf, and
+// support for loading based on that.
+
+// Copied from docker/go-plugins-helpers/volume/api.go - not exported, so we
+// need to do this to get at them.
+// These are well-established paths that should not change unless the plugin API
+// version changes.
+var (
+ activatePath = "/Plugin.Activate"
+ createPath = "/VolumeDriver.Create"
+ getPath = "/VolumeDriver.Get"
+ listPath = "/VolumeDriver.List"
+ removePath = "/VolumeDriver.Remove"
+ hostVirtualPath = "/VolumeDriver.Path"
+ mountPath = "/VolumeDriver.Mount"
+ unmountPath = "/VolumeDriver.Unmount"
+ // nolint
+ capabilitiesPath = "/VolumeDriver.Capabilities"
+)
+
+const (
+ defaultTimeout = 5 * time.Second
+ defaultPath = "/run/docker/plugins"
+ volumePluginType = "VolumeDriver"
+)
+
+var (
+ ErrNotPlugin = errors.New("target does not appear to be a valid plugin")
+ ErrNotVolumePlugin = errors.New("plugin is not a volume plugin")
+ ErrPluginRemoved = errors.New("plugin is no longer available (shut down?)")
+
+ // This stores available, initialized volume plugins.
+ pluginsLock sync.Mutex
+ plugins map[string]*VolumePlugin
+)
+
+// VolumePlugin is a single volume plugin.
+type VolumePlugin struct {
+ // Name is the name of the volume plugin. This will be used to refer to
+ // it.
+ Name string
+ // SocketPath is the unix socket at which the plugin is accessed.
+ SocketPath string
+}
+
+// This is the response from the activate endpoint of the API.
+type activateResponse struct {
+ Implements []string
+}
+
+// Validate that the given plugin is good to use.
+// Add it to available plugins if so.
+func validatePlugin(newPlugin *VolumePlugin) error {
+ // It's a socket. Is it a plugin?
+ // Hit the Activate endpoint to find out if it is, and if so what kind
+ req, err := http.NewRequest("POST", activatePath, nil)
+ if err != nil {
+ return errors.Wrapf(err, "error making request to volume plugin %s activation endpoint", newPlugin.Name)
+ }
+
+ req.Header.Set("Host", newPlugin.getURI())
+ req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1)
+
+ client := new(http.Client)
+ client.Timeout = defaultTimeout
+ resp, err := client.Do(req)
+ if err != nil {
+ return errors.Wrapf(err, "error sending request to plugin %s activation endpoint", newPlugin.Name)
+ }
+ defer resp.Body.Close()
+
+ // Response code MUST be 200. Anything else, we have to assume it's not
+ // a valid plugin.
+ if resp.StatusCode != 200 {
+ return errors.Wrapf(ErrNotPlugin, "got status code %d from activation endpoint for plugin %s", resp.StatusCode, newPlugin.Name)
+ }
+
+ // Read and decode the body so we can tell if this is a volume plugin.
+ respBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return errors.Wrapf(err, "error reading activation response body from plugin %s", newPlugin.Name)
+ }
+
+ respStruct := new(activateResponse)
+ if err := json.Unmarshal(respBytes, respStruct); err != nil {
+ return errors.Wrapf(err, "error unmarshalling plugin %s activation response", newPlugin.Name)
+ }
+
+ foundVolume := false
+ for _, pluginType := range respStruct.Implements {
+ if pluginType == volumePluginType {
+ foundVolume = true
+ break
+ }
+ }
+
+ if !foundVolume {
+ return errors.Wrapf(ErrNotVolumePlugin, "plugin %s does not implement volume plugin, instead provides %s", newPlugin.Name, strings.Join(respStruct.Implements, ", "))
+ }
+
+ plugins[newPlugin.Name] = newPlugin
+
+ return nil
+}
+
+// GetVolumePlugin gets a single volume plugin by path.
+// TODO: We should not be auto-completing based on a default path; we should
+// require volumes to have been pre-specified in containers.conf (will need a
+// function to pre-populate the plugins list, and we should probably do a lazy
+// initialization there to not slow things down too much).
+func GetVolumePlugin(name string) (*VolumePlugin, error) {
+ pluginsLock.Lock()
+ defer pluginsLock.Unlock()
+
+ plugin, exists := plugins[name]
+ if exists {
+ return plugin, nil
+ }
+
+ // It's not cached. We need to get it.
+
+ newPlugin := new(VolumePlugin)
+ newPlugin.Name = name
+ newPlugin.SocketPath = filepath.Join(defaultPath, fmt.Sprintf("%s.sock", name))
+
+ stat, err := os.Stat(newPlugin.SocketPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot access plugin %s socket %q", name, newPlugin.SocketPath)
+ }
+ if stat.Mode()&os.ModeSocket == 0 {
+ return nil, errors.Wrapf(ErrNotPlugin, "volume %s path %q is not a unix socket", name, newPlugin.SocketPath)
+ }
+
+ if err := validatePlugin(newPlugin); err != nil {
+ return nil, err
+ }
+
+ return newPlugin, nil
+}
+
+func (p *VolumePlugin) getURI() string {
+ return "unix://" + p.SocketPath
+}
+
+// Verify the plugin is still available.
+// TODO: Do we want to ping with an HTTP request? There's no ping endpoint so
+// we'd need to hit Activate or Capabilities?
+func (p *VolumePlugin) verifyReachable() error {
+ if _, err := os.Stat(p.SocketPath); err != nil {
+ if os.IsNotExist(err) {
+ pluginsLock.Lock()
+ defer pluginsLock.Unlock()
+ delete(plugins, p.Name)
+ return errors.Wrapf(ErrPluginRemoved, p.Name)
+ }
+
+ return errors.Wrapf(err, "error accessing plugin %s", p.Name)
+ }
+ return nil
+}
+
+// Send a request to the volume plugin for handling.
+func (p *VolumePlugin) sendRequest(toJSON interface{}, hasBody bool, endpoint string) (*http.Response, error) {
+ var (
+ reqJSON []byte
+ err error
+ )
+
+ if hasBody {
+ reqJSON, err = json.Marshal(toJSON)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error marshalling request JSON for volume plugin %s endpoint %s", p.Name, endpoint)
+ }
+ }
+
+ req, err := http.NewRequest("POST", endpoint, bytes.NewReader(reqJSON))
+ if err != nil {
+ return nil, errors.Wrapf(err, "error making request to volume plugin %s endpoint %s", p.Name, endpoint)
+ }
+
+ req.Header.Set("Host", p.getURI())
+ req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1)
+
+ client := new(http.Client)
+ client.Timeout = defaultTimeout
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error sending request to volume plugin %s endpoint %s", p.Name, endpoint)
+ }
+ defer resp.Body.Close()
+
+ return resp, nil
+}
+
+// Turn an error response from a volume plugin into a well-formatted Go error.
+func (p *VolumePlugin) makeErrorResponse(err, endpoint, volName string) error {
+ if err == "" {
+ err = "empty error from plugin"
+ }
+ if volName != "" {
+ return errors.Wrapf(errors.New(err), "error on %s on volume %s in volume plugin %s", endpoint, volName, p.Name)
+ } else {
+ return errors.Wrapf(errors.New(err), "error on %s in volume plugin %s", endpoint, p.Name)
+ }
+}
+
+// Handle error responses from plugin
+func (p *VolumePlugin) handleErrorResponse(resp *http.Response, endpoint, volName string) error {
+ // The official plugin reference implementation uses HTTP 500 for
+ // errors, but I don't think we can guarantee all plugins do that.
+ // Let's interpret anything other than 200 as an error.
+ // If there isn't an error, don't even bother decoding the response.
+ if resp.StatusCode != 200 {
+ errResp, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name)
+ }
+
+ errStruct := new(volume.ErrorResponse)
+ if err := json.Unmarshal(errResp, errStruct); err != nil {
+ return errors.Wrapf(err, "error unmarshalling JSON response from volume plugin %s", p.Name)
+ }
+
+ return p.makeErrorResponse(errStruct.Err, endpoint, volName)
+ }
+
+ return nil
+}
+
+// CreateVolume creates a volume in the plugin.
+func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error {
+ if req == nil {
+ return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to CreateVolume")
+ }
+
+ if err := p.verifyReachable(); err != nil {
+ return err
+ }
+
+ logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name)
+
+ resp, err := p.sendRequest(req, true, createPath)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return p.handleErrorResponse(resp, createPath, req.Name)
+}
+
+// ListVolumes lists volumes available in the plugin.
+func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) {
+ if err := p.verifyReachable(); err != nil {
+ return nil, err
+ }
+
+ logrus.Infof("Listing volumes using plugin %s", p.Name)
+
+ resp, err := p.sendRequest(nil, false, listPath)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if err := p.handleErrorResponse(resp, listPath, ""); err != nil {
+ return nil, err
+ }
+
+ // TODO: Can probably unify response reading under a helper
+ volumeRespBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name)
+ }
+
+ volumeResp := new(volume.ListResponse)
+ if err := json.Unmarshal(volumeRespBytes, volumeResp); err != nil {
+ return nil, errors.Wrapf(err, "error unmarshalling volume plugin %s list response", p.Name)
+ }
+
+ return volumeResp.Volumes, nil
+}
+
+// GetVolume gets a single volume from the plugin.
+func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error) {
+ if req == nil {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to GetVolume")
+ }
+
+ if err := p.verifyReachable(); err != nil {
+ return nil, err
+ }
+
+ logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name)
+
+ resp, err := p.sendRequest(req, true, getPath)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if err := p.handleErrorResponse(resp, getPath, req.Name); err != nil {
+ return nil, err
+ }
+
+ getRespBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name)
+ }
+
+ getResp := new(volume.GetResponse)
+ if err := json.Unmarshal(getRespBytes, getResp); err != nil {
+ return nil, errors.Wrapf(err, "error unmarshalling volume plugin %s get response", p.Name)
+ }
+
+ return getResp.Volume, nil
+}
+
+// RemoveVolume removes a single volume from the plugin.
+func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error {
+ if req == nil {
+ return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to RemoveVolume")
+ }
+
+ if err := p.verifyReachable(); err != nil {
+ return err
+ }
+
+ logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name)
+
+ resp, err := p.sendRequest(req, true, removePath)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return p.handleErrorResponse(resp, removePath, req.Name)
+}
+
+// GetVolumePath gets the path the given volume is mounted at.
+func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) {
+ if req == nil {
+ return "", errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to GetVolumePath")
+ }
+
+ if err := p.verifyReachable(); err != nil {
+ return "", err
+ }
+
+ logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name)
+
+ resp, err := p.sendRequest(req, true, hostVirtualPath)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ if err := p.handleErrorResponse(resp, hostVirtualPath, req.Name); err != nil {
+ return "", err
+ }
+
+ pathRespBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name)
+ }
+
+ pathResp := new(volume.PathResponse)
+ if err := json.Unmarshal(pathRespBytes, pathResp); err != nil {
+ return "", errors.Wrapf(err, "error unmarshalling volume plugin %s path response", p.Name)
+ }
+
+ return pathResp.Mountpoint, nil
+}
+
+// MountVolume mounts the given volume. The ID argument is the ID of the
+// mounting container, used for internal record-keeping by the plugin. Returns
+// the path the volume has been mounted at.
+func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) {
+ if req == nil {
+ return "", errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to MountVolume")
+ }
+
+ if err := p.verifyReachable(); err != nil {
+ return "", err
+ }
+
+ logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
+
+ resp, err := p.sendRequest(req, true, mountPath)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ if err := p.handleErrorResponse(resp, mountPath, req.Name); err != nil {
+ return "", err
+ }
+
+ mountRespBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name)
+ }
+
+ mountResp := new(volume.MountResponse)
+ if err := json.Unmarshal(mountRespBytes, mountResp); err != nil {
+ return "", errors.Wrapf(err, "error unmarshalling volume plugin %s path response", p.Name)
+ }
+
+ return mountResp.Mountpoint, nil
+}
+
+// UnmountVolume unmounts the given volume. The ID argument is the ID of the
+// container that is unmounting, used for internal record-keeping by the plugin.
+func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error {
+ if req == nil {
+ return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to UnmountVolume")
+ }
+
+ if err := p.verifyReachable(); err != nil {
+ return err
+ }
+
+ logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
+
+ resp, err := p.sendRequest(req, true, unmountPath)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return p.handleErrorResponse(resp, unmountPath, req.Name)
+}
diff --git a/libpod/reset.go b/libpod/reset.go
index f8828fed4..6d2842723 100644
--- a/libpod/reset.go
+++ b/libpod/reset.go
@@ -46,7 +46,7 @@ func (r *Runtime) Reset(ctx context.Context) error {
}
}
- if err := stopPauseProcess(); err != nil {
+ if err := r.stopPauseProcess(); err != nil {
logrus.Errorf("Error stopping pause process: %v", err)
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index df3dfae2b..72bd34a5e 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -387,8 +387,8 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
// Don't fatally error.
// This will allow us to ship configs including optional
// runtimes that might not be installed (crun, kata).
- // Only a warnf so default configs don't spec errors.
- logrus.Warnf("Error initializing configured OCI runtime %s: %v", name, err)
+ // Only a infof so default configs don't spec errors.
+ logrus.Infof("Error initializing configured OCI runtime %s: %v", name, err)
continue
}
@@ -472,7 +472,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
// we will need to access the storage.
if os.Geteuid() != 0 {
aliveLock.Unlock() // Unlock to avoid deadlock as BecomeRootInUserNS will reexec.
- pausePid, err := util.GetRootlessPauseProcessPidPath()
+ pausePid, err := util.GetRootlessPauseProcessPidPathGivenDir(runtime.config.Engine.TmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
@@ -538,6 +538,15 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
return nil
}
+// TmpDir gets the current Libpod temporary files directory.
+func (r *Runtime) TmpDir() (string, error) {
+ if !r.valid {
+ return "", define.ErrRuntimeStopped
+ }
+
+ return r.config.Engine.TmpDir, nil
+}
+
// GetConfig returns a copy of the configuration used by the runtime
func (r *Runtime) GetConfig() (*config.Config, error) {
r.lock.RLock()
diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go
index 61fdd42d3..6ee8a9354 100644
--- a/libpod/runtime_cstorage.go
+++ b/libpod/runtime_cstorage.go
@@ -103,7 +103,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
if errors.Cause(err) == storage.ErrContainerUnknown {
// Container was removed from under us.
// It's gone, so don't bother erroring.
- logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return errors.Wrapf(err, "error looking up container %q mounts", idOrName)
@@ -114,7 +114,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
} else if _, err := r.store.Unmount(ctr.ID, true); err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
// Container again gone, no error
- logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return errors.Wrapf(err, "error unmounting container %q", idOrName)
@@ -123,7 +123,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
if err := r.store.DeleteContainer(ctr.ID); err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
// Container again gone, no error
- logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return errors.Wrapf(err, "error removing storage for container %q", idOrName)
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index e57890fa2..a2d9a875e 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -8,7 +8,6 @@ import (
"net/http"
"net/url"
"os"
- "strings"
"github.com/containers/buildah/imagebuildah"
"github.com/containers/image/v5/directory"
@@ -276,56 +275,47 @@ func DownloadFromFile(reader *os.File) (string, error) {
}
// LoadImage loads a container image into local storage
-func (r *Runtime) LoadImage(ctx context.Context, name, inputFile string, writer io.Writer, signaturePolicy string) (string, error) {
- var (
- newImages []*image.Image
- err error
- src types.ImageReference
- )
+func (r *Runtime) LoadImage(ctx context.Context, inputFile string, writer io.Writer, signaturePolicy string) (string, error) {
+ if newImages, err := r.LoadAllImageFromArchive(ctx, writer, inputFile, signaturePolicy); err == nil {
+ return newImages, nil
+ }
+ return r.LoadImageFromSingleImageArchive(ctx, writer, inputFile, signaturePolicy)
+}
- if name == "" {
- newImages, err = r.ImageRuntime().LoadAllImagesFromDockerArchive(ctx, inputFile, signaturePolicy, writer)
- if err == nil {
- return getImageNames(newImages), nil
- }
+// LoadAllImageFromArchive loads all images from the archive of multi-image that inputFile points to.
+func (r *Runtime) LoadAllImageFromArchive(ctx context.Context, writer io.Writer, inputFile, signaturePolicy string) (string, error) {
+ newImages, err := r.ImageRuntime().LoadAllImagesFromDockerArchive(ctx, inputFile, signaturePolicy, writer)
+ if err == nil {
+ return getImageNames(newImages), nil
}
+ return "", err
+}
+// LoadImageFromSingleImageArchive load image from the archive of single image that inputFile points to.
+func (r *Runtime) LoadImageFromSingleImageArchive(ctx context.Context, writer io.Writer, inputFile, signaturePolicy string) (string, error) {
+ var err error
for _, referenceFn := range []func() (types.ImageReference, error){
func() (types.ImageReference, error) {
return dockerarchive.ParseReference(inputFile)
},
func() (types.ImageReference, error) {
- return ociarchive.NewReference(inputFile, name) // name may be ""
- },
- func() (types.ImageReference, error) {
- // prepend "localhost/" to support local image saved with this semantics
- if !strings.Contains(name, "/") {
- return ociarchive.NewReference(inputFile, fmt.Sprintf("%s/%s", image.DefaultLocalRegistry, name))
- }
- return nil, nil
+ return ociarchive.NewReference(inputFile, "")
},
func() (types.ImageReference, error) {
return directory.NewReference(inputFile)
},
func() (types.ImageReference, error) {
- return layout.NewReference(inputFile, name)
- },
- func() (types.ImageReference, error) {
- // prepend "localhost/" to support local image saved with this semantics
- if !strings.Contains(name, "/") {
- return layout.NewReference(inputFile, fmt.Sprintf("%s/%s", image.DefaultLocalRegistry, name))
- }
- return nil, nil
+ return layout.NewReference(inputFile, "")
},
} {
- src, err = referenceFn()
+ src, err := referenceFn()
if err == nil && src != nil {
- if newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer); err == nil {
+ if newImages, err := r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer); err == nil {
return getImageNames(newImages), nil
}
}
}
- return "", errors.Wrapf(err, "error pulling %q", name)
+ return "", errors.Wrapf(err, "error pulling image")
}
func getImageNames(images []*image.Image) string {
diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go
index 1ad32fe9c..f0f800ef0 100644
--- a/libpod/runtime_migrate.go
+++ b/libpod/runtime_migrate.go
@@ -18,9 +18,9 @@ import (
"github.com/sirupsen/logrus"
)
-func stopPauseProcess() error {
+func (r *Runtime) stopPauseProcess() error {
if rootless.IsRootless() {
- pausePidPath, err := util.GetRootlessPauseProcessPidPath()
+ pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(r.config.Engine.TmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
@@ -98,5 +98,5 @@ func (r *Runtime) migrate(ctx context.Context) error {
}
}
- return stopPauseProcess()
+ return r.stopPauseProcess()
}
diff --git a/libpod/runtime_migrate_unsupported.go b/libpod/runtime_migrate_unsupported.go
index e362cca63..a9d351318 100644
--- a/libpod/runtime_migrate_unsupported.go
+++ b/libpod/runtime_migrate_unsupported.go
@@ -10,6 +10,6 @@ func (r *Runtime) migrate(ctx context.Context) error {
return nil
}
-func stopPauseProcess() error {
+func (r *Runtime) stopPauseProcess() error {
return nil
}
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index 76419587a..3e4185db1 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -34,40 +34,56 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
// Set Pod hostname
g.Config.Hostname = p.config.Hostname
+ var options []CtrCreateOption
+
+ // Command: If user-specified, use that preferentially.
+ // If not set and the config file is set, fall back to that.
+ var infraCtrCommand []string
+ if p.config.InfraContainer.InfraCommand != nil {
+ logrus.Debugf("User-specified infra container entrypoint %v", p.config.InfraContainer.InfraCommand)
+ infraCtrCommand = p.config.InfraContainer.InfraCommand
+ } else if r.config.Engine.InfraCommand != "" {
+ logrus.Debugf("Config-specified infra container entrypoint %s", r.config.Engine.InfraCommand)
+ infraCtrCommand = []string{r.config.Engine.InfraCommand}
+ }
+ // Only if set by the user or containers.conf, we set entrypoint for the
+ // infra container.
+ // This is only used by commit, so it shouldn't matter... But someone
+ // may eventually want to commit an infra container?
+ // TODO: Should we actually do this if set by containers.conf?
+ if infraCtrCommand != nil {
+ // Need to duplicate the array - we are going to add Cmd later
+ // so the current array will be changed.
+ newArr := make([]string, 0, len(infraCtrCommand))
+ newArr = append(newArr, infraCtrCommand...)
+ options = append(options, WithEntrypoint(newArr))
+ }
+
isRootless := rootless.IsRootless()
- entrypointSet := len(p.config.InfraContainer.InfraCommand) > 0
- entryPoint := p.config.InfraContainer.InfraCommand
- entryCmd := []string{}
- var options []CtrCreateOption
// I've seen circumstances where config is being passed as nil.
// Let's err on the side of safety and make sure it's safe to use.
if config != nil {
- // default to entrypoint in image if there is one
- if !entrypointSet {
- if len(config.Entrypoint) > 0 {
- entrypointSet = true
- entryPoint = config.Entrypoint
- entryCmd = config.Entrypoint
+ if infraCtrCommand == nil {
+ // If we have no entrypoint and command from the image,
+ // we can't go on - the infra container has no command.
+ if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
+ return nil, errors.Errorf("infra container has no command")
}
- } else { // so use the InfraCommand
- entrypointSet = true
- entryCmd = entryPoint
- }
-
- if len(config.Cmd) > 0 {
- // We can't use the default pause command, since we're
- // sourcing from the image. If we didn't already set an
- // entrypoint, set one now.
- if !entrypointSet {
+ if len(config.Entrypoint) > 0 {
+ infraCtrCommand = config.Entrypoint
+ } else {
// Use the Docker default "/bin/sh -c"
// entrypoint, as we're overriding command.
// If an image doesn't want this, it can
// override entrypoint too.
- entryCmd = []string{"/bin/sh", "-c"}
+ infraCtrCommand = []string{"/bin/sh", "-c"}
}
- entryCmd = append(entryCmd, config.Cmd...)
}
+ if len(config.Cmd) > 0 {
+ infraCtrCommand = append(infraCtrCommand, config.Cmd...)
+ }
+
if len(config.Env) > 0 {
for _, nameValPair := range config.Env {
nameValSlice := strings.Split(nameValPair, "=")
@@ -127,9 +143,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
}
g.SetRootReadonly(true)
- g.SetProcessArgs(entryCmd)
+ g.SetProcessArgs(infraCtrCommand)
- logrus.Debugf("Using %q as infra container entrypoint", entryCmd)
+ logrus.Debugf("Using %q as infra container command", infraCtrCommand)
g.RemoveMount("/dev/shm")
if isRootless {
@@ -148,9 +164,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
options = append(options, WithRootFSFromImage(imgID, imgName, rawImageName))
options = append(options, WithName(containerName))
options = append(options, withIsInfra())
- if entrypointSet {
- options = append(options, WithEntrypoint(entryPoint))
- }
if len(p.config.InfraContainer.ConmonPidFile) > 0 {
options = append(options, WithConmonPidFile(p.config.InfraContainer.ConmonPidFile))
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 25598ce4d..1eb42660c 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -117,7 +117,7 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (_ *Po
return nil, errors.Errorf("Pods must have an infra container to share namespaces")
}
if pod.HasInfraContainer() && !pod.SharesNamespaces() {
- logrus.Warnf("Pod has an infra container, but shares no namespaces")
+ logrus.Infof("Pod has an infra container, but shares no namespaces")
}
if err := r.state.AddPod(pod); err != nil {
@@ -212,7 +212,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
// Don't try if we failed to retrieve the cgroup
if err == nil {
if err := conmonCgroup.Update(resLimits); err != nil {
- logrus.Warnf("Error updating pod %s conmon cgroup %s PID limit: %v", p.ID(), conmonCgroupPath, err)
+ logrus.Warnf("Error updating pod %s conmon cgroup PID limit: %v", p.ID(), err)
}
}
}
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 5886455e7..7a3e5dd84 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -17,6 +17,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
+ "github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -73,7 +74,7 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
func ListContainers(w http.ResponseWriter, r *http.Request) {
@@ -207,7 +208,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
}
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
func WaitContainer(w http.ResponseWriter, r *http.Request) {
@@ -215,8 +216,10 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/wait
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
+ logrus.Warnf("failed to wait on container %q: %v", mux.Vars(r)["name"], err)
return
}
+
utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
Error: struct {
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go
index 1dd563393..223eb2cd5 100644
--- a/pkg/api/handlers/compat/containers_archive.go
+++ b/pkg/api/handlers/compat/containers_archive.go
@@ -5,24 +5,16 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
- "path/filepath"
- "strings"
-
- "github.com/containers/buildah/copier"
- "github.com/containers/buildah/pkg/chrootuser"
- "github.com/containers/podman/v2/libpod"
- "github.com/containers/podman/v2/libpod/define"
- "github.com/containers/podman/v2/pkg/api/handlers/utils"
- "github.com/containers/storage/pkg/idtools"
- "github.com/opencontainers/runtime-spec/specs-go"
-
"net/http"
"os"
"time"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/copy"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
func Archive(w http.ResponseWriter, r *http.Request) {
@@ -32,14 +24,14 @@ func Archive(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPut:
handlePut(w, r, decoder, runtime)
- case http.MethodGet, http.MethodHead:
- handleHeadOrGet(w, r, decoder, runtime)
+ case http.MethodHead, http.MethodGet:
+ handleHeadAndGet(w, r, decoder, runtime)
default:
- utils.Error(w, fmt.Sprintf("not implemented, method: %v", r.Method), http.StatusNotImplemented, errors.New(fmt.Sprintf("not implemented, method: %v", r.Method)))
+ utils.Error(w, fmt.Sprintf("unsupported method: %v", r.Method), http.StatusNotImplemented, errors.New(fmt.Sprintf("unsupported method: %v", r.Method)))
}
}
-func handleHeadOrGet(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
query := struct {
Path string `schema:"path"`
}{}
@@ -66,170 +58,62 @@ func handleHeadOrGet(w http.ResponseWriter, r *http.Request, decoder *schema.Dec
return
}
- mountPoint, err := ctr.Mount()
+ source, err := copy.CopyItemForContainer(ctr, query.Path, true, true)
+ defer source.CleanUp()
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to mount the container"))
+ utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrapf(err, "error stating container path %q", query.Path))
return
}
- defer func() {
- if err := ctr.Unmount(false); err != nil {
- logrus.Warnf("failed to unmount container %s: %q", containerName, err)
- }
- }()
-
- opts := copier.StatOptions{}
-
- mountPoint, path, err := fixUpMountPointAndPath(runtime, ctr, mountPoint, query.Path)
+ // NOTE: Docker always sets the header.
+ info, err := source.Stat()
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrapf(err, "error stating container path %q", query.Path))
return
}
-
- stats, err := copier.Stat(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)})
+ statHeader, err := fileInfoToDockerStats(info)
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to get stats about file"))
- return
- }
-
- if len(stats) <= 0 || len(stats[0].Globbed) <= 0 {
- errs := make([]string, 0, len(stats))
-
- for _, stat := range stats {
- if stat.Error != "" {
- errs = append(errs, stat.Error)
- }
- }
-
- utils.Error(w, "Not found.", http.StatusNotFound, fmt.Errorf("file doesn't exist (errs: %q)", strings.Join(errs, ";")))
-
- return
- }
-
- statHeader, err := statsToHeader(stats[0].Results[stats[0].Globbed[0]])
- if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
-
w.Header().Add("X-Docker-Container-Path-Stat", statHeader)
- if r.Method == http.MethodGet {
- idMappingOpts, err := ctr.IDMappings()
- if err != nil {
- utils.Error(w, "Not found.", http.StatusInternalServerError,
- errors.Wrapf(err, "error getting IDMappingOptions"))
- return
- }
-
- destOwner := idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
-
- opts := copier.GetOptions{
- UIDMap: idMappingOpts.UIDMap,
- GIDMap: idMappingOpts.GIDMap,
- ChownDirs: &destOwner,
- ChownFiles: &destOwner,
- KeepDirectoryNames: true,
- }
-
- w.WriteHeader(http.StatusOK)
-
- err = copier.Get(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)}, w)
- if err != nil {
- logrus.Error(errors.Wrapf(err, "failed to copy from the %s container path %s", containerName, query.Path))
- return
- }
- } else {
+ // Our work is done when the user is interested in the header only.
+ if r.Method == http.MethodHead {
w.WriteHeader(http.StatusOK)
- }
-}
-
-func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
- query := struct {
- Path string `schema:"path"`
- // TODO handle params below
- NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
- CopyUIDGID bool `schema:"copyUIDGID"`
- }{}
-
- err := decoder.Decode(&query, r.URL.Query())
- if err != nil {
- utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
return
}
- ctrName := utils.GetName(r)
-
- ctr, err := runtime.LookupContainer(ctrName)
- if err != nil {
- utils.Error(w, "Not found", http.StatusNotFound, errors.Wrapf(err, "the %s container doesn't exists", ctrName))
- return
- }
-
- mountPoint, err := ctr.Mount()
- if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, errors.Wrapf(err, "failed to mount the %s container", ctrName))
- return
- }
-
- defer func() {
- if err := ctr.Unmount(false); err != nil {
- logrus.Warnf("failed to unmount container %s", ctrName)
- }
- }()
-
- user, err := getUser(mountPoint, ctr.User())
- if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
- return
- }
-
- idMappingOpts, err := ctr.IDMappings()
- if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, errors.Wrapf(err, "error getting IDMappingOptions"))
- return
- }
-
- destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
-
- opts := copier.PutOptions{
- UIDMap: idMappingOpts.UIDMap,
- GIDMap: idMappingOpts.GIDMap,
- ChownDirs: &destOwner,
- ChownFiles: &destOwner,
- }
-
- mountPoint, path, err := fixUpMountPointAndPath(runtime, ctr, mountPoint, query.Path)
+ // Alright, the users wants data from the container.
+ destination, err := copy.CopyItemForWriter(w)
if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
w.WriteHeader(http.StatusOK)
-
- err = copier.Put(mountPoint, filepath.Join(mountPoint, path), opts, r.Body)
- if err != nil {
- logrus.Error(errors.Wrapf(err, "failed to copy to the %s container path %s", ctrName, query.Path))
+ if err := copy.Copy(&source, &destination, false); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
}
-func statsToHeader(stats *copier.StatForItem) (string, error) {
- statsDTO := struct {
+func fileInfoToDockerStats(info *copy.FileInfo) (string, error) {
+ dockerStats := struct {
Name string `json:"name"`
Size int64 `json:"size"`
Mode os.FileMode `json:"mode"`
ModTime time.Time `json:"mtime"`
LinkTarget string `json:"linkTarget"`
}{
- Name: filepath.Base(stats.Name),
- Size: stats.Size,
- Mode: stats.Mode,
- ModTime: stats.ModTime,
- LinkTarget: stats.ImmediateTarget,
+ Name: info.Name,
+ Size: info.Size,
+ Mode: info.Mode,
+ ModTime: info.ModTime,
+ LinkTarget: info.LinkTarget,
}
- jsonBytes, err := json.Marshal(&statsDTO)
+ jsonBytes, err := json.Marshal(&dockerStats)
if err != nil {
return "", errors.Wrap(err, "failed to serialize file stats")
}
@@ -250,130 +134,45 @@ func statsToHeader(stats *copier.StatForItem) (string, error) {
return buff.String(), nil
}
-// the utility functions below are copied from abi/cp.go
-
-func getUser(mountPoint string, userspec string) (specs.User, error) {
- uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
- u := specs.User{
- UID: uid,
- GID: gid,
- Username: userspec,
- }
-
- if !strings.Contains(userspec, ":") {
- groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
- if err2 != nil {
- if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
- err = err2
- }
- } else {
- u.AdditionalGids = groups
- }
- }
-
- return u, err
-}
-
-func fixUpMountPointAndPath(runtime *libpod.Runtime, ctr *libpod.Container, mountPoint, ctrPath string) (string, string, error) {
- if !filepath.IsAbs(ctrPath) {
- endsWithSep := strings.HasSuffix(ctrPath, string(filepath.Separator))
- ctrPath = filepath.Join(ctr.WorkingDir(), ctrPath)
-
- if endsWithSep {
- ctrPath = ctrPath + string(filepath.Separator)
- }
- }
- if isVol, volDestName, volName := isVolumeDestName(ctrPath, ctr); isVol { //nolint(gocritic)
- newMountPoint, path, err := pathWithVolumeMount(runtime, volDestName, volName, ctrPath)
- if err != nil {
- return "", "", errors.Wrapf(err, "error getting source path from volume %s", volDestName)
- }
-
- mountPoint = newMountPoint
- ctrPath = path
- } else if isBindMount, mount := isBindMountDestName(ctrPath, ctr); isBindMount { //nolint(gocritic)
- newMountPoint, path := pathWithBindMountSource(mount, ctrPath)
- mountPoint = newMountPoint
- ctrPath = path
- }
-
- return mountPoint, ctrPath, nil
-}
-
-func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
- separator := string(os.PathSeparator)
-
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, separator)
- }
-
- if path == "" {
- return false, "", ""
- }
-
- for _, vol := range ctr.Config().NamedVolumes {
- volNamePath := strings.TrimPrefix(vol.Dest, separator)
- if matchVolumePath(path, volNamePath) {
- return true, vol.Dest, vol.Name
- }
- }
-
- return false, "", ""
-}
+func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+ query := struct {
+ Path string `schema:"path"`
+ // TODO handle params below
+ NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
+ CopyUIDGID bool `schema:"copyUIDGID"`
+ }{}
-func pathWithVolumeMount(runtime *libpod.Runtime, volDestName, volName, path string) (string, string, error) {
- destVolume, err := runtime.GetVolume(volName)
+ err := decoder.Decode(&query, r.URL.Query())
if err != nil {
- return "", "", errors.Wrapf(err, "error getting volume destination %s", volName)
- }
-
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
+ utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
+ return
}
- return destVolume.MountPoint(), strings.TrimPrefix(path, volDestName), err
-}
-
-func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
- separator := string(os.PathSeparator)
-
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, string(os.PathSeparator))
- }
+ ctrName := utils.GetName(r)
- if path == "" {
- return false, specs.Mount{}
+ ctr, err := runtime.LookupContainer(ctrName)
+ if err != nil {
+ utils.Error(w, "Not found", http.StatusNotFound, errors.Wrapf(err, "the %s container doesn't exists", ctrName))
+ return
}
- for _, m := range ctr.Config().Spec.Mounts {
- if m.Type != "bind" {
- continue
- }
-
- mDest := strings.TrimPrefix(m.Destination, separator)
- if matchVolumePath(path, mDest) {
- return true, m
- }
+ destination, err := copy.CopyItemForContainer(ctr, query.Path, true, false)
+ defer destination.CleanUp()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
}
- return false, specs.Mount{}
-}
-
-func matchVolumePath(path, target string) bool {
- pathStr := filepath.Clean(path)
- target = filepath.Clean(target)
-
- for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
- pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
+ source, err := copy.CopyItemForReader(r.Body)
+ defer source.CleanUp()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
}
- return pathStr == target
-}
-
-func pathWithBindMountSource(m specs.Mount, path string) (string, string) {
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
+ w.WriteHeader(http.StatusOK)
+ if err := copy.Copy(&source, &destination, false); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
}
-
- return m.Source, strings.TrimPrefix(path, m.Destination)
}
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 729639928..409a74de2 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -37,6 +37,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
return
}
+ // Override the container name in the body struct
+ body.Name = query.Name
+
if len(body.HostConfig.Links) > 0 {
utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter"))
return
@@ -69,9 +72,6 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
return
}
- // Override the container name in the body struct
- body.Name = query.Name
-
ic := abi.ContainerEngine{Libpod: runtime}
report, err := ic.ContainerCreate(r.Context(), sg)
if err != nil {
diff --git a/pkg/api/handlers/compat/containers_pause.go b/pkg/api/handlers/compat/containers_pause.go
index 8712969c0..a7e0a66f1 100644
--- a/pkg/api/handlers/compat/containers_pause.go
+++ b/pkg/api/handlers/compat/containers_pause.go
@@ -24,5 +24,5 @@ func PauseContainer(w http.ResponseWriter, r *http.Request) {
return
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_restart.go b/pkg/api/handlers/compat/containers_restart.go
index f4d8f06a1..e8928596a 100644
--- a/pkg/api/handlers/compat/containers_restart.go
+++ b/pkg/api/handlers/compat/containers_restart.go
@@ -41,5 +41,5 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) {
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go
index 6236b1357..726da6f99 100644
--- a/pkg/api/handlers/compat/containers_start.go
+++ b/pkg/api/handlers/compat/containers_start.go
@@ -39,12 +39,12 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
return
}
if state == define.ContainerStateRunning {
- utils.WriteResponse(w, http.StatusNotModified, "")
+ utils.WriteResponse(w, http.StatusNotModified, nil)
return
}
if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_stop.go b/pkg/api/handlers/compat/containers_stop.go
index 13fe25338..8bc58cf59 100644
--- a/pkg/api/handlers/compat/containers_stop.go
+++ b/pkg/api/handlers/compat/containers_stop.go
@@ -40,7 +40,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
}
// If the Container is stopped already, send a 304
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
- utils.WriteResponse(w, http.StatusNotModified, "")
+ utils.WriteResponse(w, http.StatusNotModified, nil)
return
}
@@ -56,5 +56,5 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_unpause.go b/pkg/api/handlers/compat/containers_unpause.go
index f87b95b64..760e85814 100644
--- a/pkg/api/handlers/compat/containers_unpause.go
+++ b/pkg/api/handlers/compat/containers_unpause.go
@@ -24,5 +24,5 @@ func UnpauseContainer(w http.ResponseWriter, r *http.Request) {
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index d177b2335..a51dd8ed3 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -390,7 +390,7 @@ func LoadImages(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
return
}
- id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "")
+ id, err := runtime.LoadImage(r.Context(), f.Name(), writer, "")
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image"))
return
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index a4bb72140..43478c1d3 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -104,9 +104,6 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
if len(query.Tag) > 0 {
output = query.Tag[0]
}
- if _, found := r.URL.Query()["target"]; found {
- output = query.Target
- }
var additionalNames []string
if len(query.Tag) > 1 {
@@ -162,7 +159,6 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
reporter := channel.NewWriter(make(chan []byte, 1))
defer reporter.Close()
-
buildOptions := imagebuildah.BuildOptions{
ContextDirectory: contextDirectory,
PullPolicy: pullPolicy,
@@ -267,7 +263,7 @@ loop:
failed = true
m.Error = string(e)
if err := enc.Encode(m); err != nil {
- logrus.Warnf("Failed to json encode error %q", err.Error())
+ logrus.Warnf("Failed to json encode error %v", err)
}
flush()
case <-runCtx.Done():
@@ -275,7 +271,7 @@ loop:
if !utils.IsLibpodRequest(r) {
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
if err := enc.Encode(m); err != nil {
- logrus.Warnf("Failed to json encode error %q", err.Error())
+ logrus.Warnf("Failed to json encode error %v", err)
}
flush()
}
diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go
index 12593a68c..0f3da53e8 100644
--- a/pkg/api/handlers/compat/images_push.go
+++ b/pkg/api/handlers/compat/images_push.go
@@ -81,5 +81,4 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, "")
-
}
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index c74cdb840..fe13971b0 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -50,7 +50,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.NetworkNotFound(w, name, err)
return
}
- report, err := getNetworkResourceByName(name, runtime)
+ report, err := getNetworkResourceByNameOrID(name, runtime, nil)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -58,7 +58,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, report)
}
-func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) {
+func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filters map[string][]string) (*types.NetworkResource, error) {
var (
ipamConfigs []dockerNetwork.IPAMConfig
)
@@ -68,7 +68,7 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
}
containerEndpoints := map[string]types.EndpointResource{}
// Get the network path so we can get created time
- networkConfigPath, err := network.GetCNIConfigPathByName(config, name)
+ networkConfigPath, err := network.GetCNIConfigPathByNameOrID(config, nameOrID)
if err != nil {
return nil, err
}
@@ -85,6 +85,16 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
if err != nil {
return nil, err
}
+ if len(filters) > 0 {
+ ok, err := network.IfPassesFilter(conf, filters)
+ if err != nil {
+ return nil, err
+ }
+ if !ok {
+ // do not return the config if we did not match the filter
+ return nil, nil
+ }
+ }
// No Bridge plugin means we bail
bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
@@ -106,7 +116,7 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
if err != nil {
return nil, err
}
- if netData, ok := data.NetworkSettings.Networks[name]; ok {
+ if netData, ok := data.NetworkSettings.Networks[conf.Name]; ok {
containerEndpoint := types.EndpointResource{
Name: netData.NetworkID,
EndpointID: netData.EndpointID,
@@ -118,8 +128,8 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
}
}
report := types.NetworkResource{
- Name: name,
- ID: name,
+ Name: conf.Name,
+ ID: network.GetNetworkID(conf.Name),
Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
Scope: "",
Driver: network.DefaultNetworkDriver,
@@ -129,14 +139,14 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
Options: nil,
Config: ipamConfigs,
},
- Internal: false,
+ Internal: !bridge.IsGW,
Attachable: false,
Ingress: false,
ConfigFrom: dockerNetwork.ConfigReference{},
ConfigOnly: false,
Containers: containerEndpoints,
Options: nil,
- Labels: nil,
+ Labels: network.GetNetworkLabels(conf),
Peers: nil,
Services: nil,
}
@@ -180,41 +190,23 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
return
}
- filterNames, nameFilterExists := query.Filters["name"]
- // TODO remove when filters are implemented
- if (!nameFilterExists && len(query.Filters) > 0) || len(query.Filters) > 1 {
- utils.InternalServerError(w, errors.New("only the name filter for listing networks is implemented"))
- return
- }
netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil {
utils.InternalServerError(w, err)
return
}
- // filter by name
- if nameFilterExists {
- names := []string{}
- for _, name := range netNames {
- for _, filter := range filterNames {
- if strings.Contains(name, filter) {
- names = append(names, name)
- break
- }
- }
- }
- netNames = names
- }
-
- reports := make([]*types.NetworkResource, 0, len(netNames))
+ var reports []*types.NetworkResource
logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames {
- report, err := getNetworkResourceByName(name, runtime)
+ report, err := getNetworkResourceByNameOrID(name, runtime, query.Filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
- reports = append(reports, report)
+ if report != nil {
+ reports = append(reports, report)
+ }
}
utils.WriteResponse(w, http.StatusOK, reports)
}
@@ -245,6 +237,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
ncOptions := entities.NetworkCreateOptions{
Driver: network.DefaultNetworkDriver,
Internal: networkCreate.Internal,
+ Labels: networkCreate.Labels,
}
if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
if len(networkCreate.IPAM.Config) > 1 {
@@ -278,11 +271,16 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
return
}
+ net, err := getNetworkResourceByNameOrID(name, runtime, nil)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
body := struct {
Id string
Warning []string
}{
- Id: name,
+ Id: net.ID,
}
utils.WriteResponse(w, http.StatusCreated, body)
}
@@ -327,7 +325,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
// Connect adds a container to a network
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
index 9f6611b30..5513e902e 100644
--- a/pkg/api/handlers/compat/ping.go
+++ b/pkg/api/handlers/compat/ping.go
@@ -15,6 +15,7 @@ import (
func Ping(w http.ResponseWriter, r *http.Request) {
// Note API-Version and Libpod-API-Version are set in handler_api.go
w.Header().Set("BuildKit-Version", "")
+ w.Header().Set("Builder-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Pragma", "no-cache")
diff --git a/pkg/api/handlers/compat/system.go b/pkg/api/handlers/compat/system.go
index 322bfa7ed..e21ae160a 100644
--- a/pkg/api/handlers/compat/system.go
+++ b/pkg/api/handlers/compat/system.go
@@ -2,17 +2,91 @@ package compat
import (
"net/http"
+ "strings"
+ "github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/domain/infra/abi"
docker "github.com/docker/docker/api/types"
)
func GetDiskUsage(w http.ResponseWriter, r *http.Request) {
+ options := entities.SystemDfOptions{}
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ ic := abi.ContainerEngine{Libpod: runtime}
+ df, err := ic.SystemDf(r.Context(), options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+
+ imgs := make([]*docker.ImageSummary, len(df.Images))
+ for i, o := range df.Images {
+ t := docker.ImageSummary{
+ Containers: int64(o.Containers),
+ Created: o.Created.Unix(),
+ ID: o.ImageID,
+ Labels: map[string]string{},
+ ParentID: "",
+ RepoDigests: nil,
+ RepoTags: []string{o.Tag},
+ SharedSize: o.SharedSize,
+ Size: o.Size,
+ VirtualSize: o.Size - o.UniqueSize,
+ }
+ imgs[i] = &t
+ }
+
+ ctnrs := make([]*docker.Container, len(df.Containers))
+ for i, o := range df.Containers {
+ t := docker.Container{
+ ID: o.ContainerID,
+ Names: []string{o.Names},
+ Image: o.Image,
+ ImageID: o.Image,
+ Command: strings.Join(o.Command, " "),
+ Created: o.Created.Unix(),
+ Ports: nil,
+ SizeRw: o.RWSize,
+ SizeRootFs: o.Size,
+ Labels: map[string]string{},
+ State: o.Status,
+ Status: o.Status,
+ HostConfig: struct {
+ NetworkMode string `json:",omitempty"`
+ }{},
+ NetworkSettings: nil,
+ Mounts: nil,
+ }
+ ctnrs[i] = &t
+ }
+
+ vols := make([]*docker.Volume, len(df.Volumes))
+ for i, o := range df.Volumes {
+ t := docker.Volume{
+ CreatedAt: "",
+ Driver: "",
+ Labels: map[string]string{},
+ Mountpoint: "",
+ Name: o.VolumeName,
+ Options: nil,
+ Scope: "local",
+ Status: nil,
+ UsageData: &docker.VolumeUsageData{
+ RefCount: 1,
+ Size: o.Size,
+ },
+ }
+ vols[i] = &t
+ }
+
utils.WriteResponse(w, http.StatusOK, handlers.DiskUsage{DiskUsage: docker.DiskUsage{
- LayersSize: 0,
- Images: nil,
- Containers: nil,
- Volumes: nil,
+ LayersSize: 0,
+ Images: imgs,
+ Containers: ctnrs,
+ Volumes: vols,
+ BuildCache: []*docker.BuildCache{},
+ BuilderSize: 0,
}})
}
diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go
index a3c9fbd2f..71b848932 100644
--- a/pkg/api/handlers/compat/volumes.go
+++ b/pkg/api/handlers/compat/volumes.go
@@ -223,7 +223,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) {
}
} else {
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
} else {
if !query.Force {
@@ -232,7 +232,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) {
// Volume does not exist and `force` is truthy - this emulates what
// Docker would do when told to `force` removal of a nonextant
// volume
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
}
}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index be5a394de..6145207ca 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -336,7 +336,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) {
}
tmpfile.Close()
- loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "")
+ loadedImage, err := runtime.LoadImage(context.Background(), tmpfile.Name(), os.Stderr, "")
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image"))
return
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index f1578f829..8511e2733 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -48,7 +48,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
- Filter string `schema:"filter"`
+ Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
@@ -59,7 +59,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
}
options := entities.NetworkListOptions{
- Filter: query.Filter,
+ Filters: query.Filters,
}
ic := abi.ContainerEngine{Libpod: runtime}
reports, err := ic.NetworkList(r.Context(), options)
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 40cf16807..c9adde09d 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -145,13 +145,14 @@ type PodCreateConfig struct {
Share string `json:"share"`
}
+// HistoryResponse provides details on image layers
type HistoryResponse struct {
- ID string `json:"Id"`
- Created int64 `json:"Created"`
- CreatedBy string `json:"CreatedBy"`
- Tags []string `json:"Tags"`
- Size int64 `json:"Size"`
- Comment string `json:"Comment"`
+ ID string `json:"Id"`
+ Created int64
+ CreatedBy string
+ Tags []string
+ Size int64
+ Comment string
}
type ImageLayer struct{}
@@ -177,55 +178,34 @@ type ExecStartConfig struct {
}
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
- containers, err := l.Containers()
- if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain Containers for image %s", l.ID())
- }
- containerCount := len(containers)
-
- // FIXME: GetParent() panics
- // parent, err := l.GetParent(context.TODO())
- // if err != nil {
- // return nil, errors.Wrapf(err, "failed to obtain ParentID for image %s", l.ID())
- // }
-
- labels, err := l.Labels(context.TODO())
- if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain Labels for image %s", l.ID())
- }
-
- size, err := l.Size(context.TODO())
+ imageData, err := l.Inspect(context.TODO())
if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain Size for image %s", l.ID())
+ return nil, errors.Wrapf(err, "failed to obtain summary for image %s", l.ID())
}
- repoTags, err := l.RepoTags()
+ containers, err := l.Containers()
if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain RepoTags for image %s", l.ID())
- }
-
- digests := make([]string, len(l.Digests()))
- for i, d := range l.Digests() {
- digests[i] = string(d)
+ return nil, errors.Wrapf(err, "failed to obtain Containers for image %s", l.ID())
}
+ containerCount := len(containers)
is := entities.ImageSummary{
ID: l.ID(),
- ParentId: l.Parent,
- RepoTags: repoTags,
+ ParentId: imageData.Parent,
+ RepoTags: imageData.RepoTags,
+ RepoDigests: imageData.RepoDigests,
Created: l.Created().Unix(),
- Size: int64(*size),
+ Size: imageData.Size,
SharedSize: 0,
- VirtualSize: l.VirtualSize,
- Labels: labels,
+ VirtualSize: imageData.VirtualSize,
+ Labels: imageData.Labels,
Containers: containerCount,
ReadOnly: l.IsReadOnly(),
Dangling: l.Dangling(),
Names: l.Names(),
- Digest: string(l.Digest()),
- Digests: digests,
+ Digest: string(imageData.Digest),
ConfigDigest: string(l.ConfigDigest),
- History: l.NamesHistory(),
+ History: imageData.NamesHistory,
}
return &is, nil
}
@@ -282,8 +262,8 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
}
}
dockerImageInspect := docker.ImageInspect{
- Architecture: l.Architecture,
- Author: l.Author,
+ Architecture: info.Architecture,
+ Author: info.Author,
Comment: info.Comment,
Config: &config,
Created: l.Created().Format(time.RFC3339Nano),
@@ -291,9 +271,9 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
GraphDriver: docker.GraphDriverData{},
ID: fmt.Sprintf("sha256:%s", l.ID()),
Metadata: docker.ImageMetadata{},
- Os: l.Os,
- OsVersion: l.Version,
- Parent: l.Parent,
+ Os: info.Os,
+ OsVersion: info.Version,
+ Parent: info.Parent,
RepoDigests: info.RepoDigests,
RepoTags: info.RepoTags,
RootFS: rootfs,
@@ -329,7 +309,6 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
dockerImageInspect.Parent = d.Parent.String()
}
return &ImageInspect{dockerImageInspect}, nil
-
}
// portsToPortSet converts libpods exposed ports to dockers structs
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index ea169cbdf..e6c85d244 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -65,7 +65,12 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// - in: query
// name: filters
// type: string
- // description: JSON encoded value of the filters (a map[string][]string) to process on the networks list. Only the name filter is supported.
+ // description: |
+ // JSON encoded value of the filters (a map[string][]string) to process on the network list. Currently available filters:
+ // - name=[name] Matches network name (accepts regex).
+ // - id=[id] Matches for full or partial ID.
+ // - driver=[driver] Only bridge is supported.
+ // - label=[key] or label=[key=value] Matches networks based on the presence of a label alone or a label and a value.
// produces:
// - application/json
// responses:
@@ -216,9 +221,15 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// description: Display summary of network configurations
// parameters:
// - in: query
- // name: filter
+ // name: filters
// type: string
- // description: Provide filter values (e.g. 'name=podman')
+ // description: |
+ // JSON encoded value of the filters (a map[string][]string) to process on the network list. Available filters:
+ // - name=[name] Matches network name (accepts regex).
+ // - id=[id] Matches for full or partial ID.
+ // - driver=[driver] Only bridge is supported.
+ // - label=[key] or label=[key=value] Matches networks based on the presence of a label alone or a label and a value.
+ // - plugin=[plugin] Matches CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`)
// produces:
// - application/json
// responses:
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index 31435ae91..f2cb3147c 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -152,7 +152,7 @@ func pingNewConnection(ctx context.Context) error {
return err
}
// the ping endpoint sits at / in this case
- response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil, nil)
+ response, err := client.DoRequest(nil, http.MethodGet, "/_ping", nil, nil)
if err != nil {
return err
}
@@ -207,11 +207,11 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (
authMethods = append(authMethods, ssh.Password(pw))
}
if len(authMethods) == 0 {
- pass, err := terminal.ReadPassword("Login password:")
- if err != nil {
- return Connection{}, err
+ callback := func() (string, error) {
+ pass, err := terminal.ReadPassword("Login password:")
+ return string(pass), err
}
- authMethods = append(authMethods, ssh.Password(string(pass)))
+ authMethods = append(authMethods, ssh.PasswordCallback(callback))
}
port := _url.Port()
diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go
index 7b321af93..91b155fc4 100644
--- a/pkg/bindings/containers/attach.go
+++ b/pkg/bindings/containers/attach.go
@@ -332,7 +332,7 @@ func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, i
case <-winChange:
h, w, err := terminal.GetSize(int(file.Fd()))
if err != nil {
- logrus.Warnf("failed to obtain TTY size: " + err.Error())
+ logrus.Warnf("failed to obtain TTY size: %v", err)
}
var resizeErr error
@@ -342,7 +342,7 @@ func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, i
resizeErr = ResizeContainerTTY(ctx, id, &h, &w)
}
if resizeErr != nil {
- logrus.Warnf("failed to resize TTY: " + resizeErr.Error())
+ logrus.Warnf("failed to resize TTY: %v", err)
}
}
}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index 1d4be8a4c..347f97703 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -2,6 +2,7 @@ package network
import (
"context"
+ "encoding/json"
"net/http"
"net/url"
"strconv"
@@ -79,8 +80,12 @@ func List(ctx context.Context, options entities.NetworkListOptions) ([]*entities
return nil, err
}
params := url.Values{}
- if options.Filter != "" {
- params.Set("filter", options.Filter)
+ if options.Filters != nil {
+ b, err := json.Marshal(options.Filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", string(b))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", params, nil)
if err != nil {
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index 0fb677768..15066ff1a 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -290,17 +290,17 @@ var _ = Describe("Podman containers ", func() {
Expect(wait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", -1))
- errChan = make(chan error)
+ unpauseErrChan := make(chan error)
go func() {
defer GinkgoRecover()
_, waitErr := containers.Wait(bt.conn, name, &running)
- errChan <- waitErr
- close(errChan)
+ unpauseErrChan <- waitErr
+ close(unpauseErrChan)
}()
err = containers.Unpause(bt.conn, name)
Expect(err).To(BeNil())
- unPausewait := <-errChan
+ unPausewait := <-unpauseErrChan
Expect(unPausewait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", -1))
})
diff --git a/pkg/copy/copy.go b/pkg/copy/copy.go
new file mode 100644
index 000000000..0e68eb450
--- /dev/null
+++ b/pkg/copy/copy.go
@@ -0,0 +1,188 @@
+package copy
+
+import (
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ buildahCopiah "github.com/containers/buildah/copier"
+ "github.com/containers/storage/pkg/archive"
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/pkg/errors"
+)
+
+// ********************************* NOTE *************************************
+//
+// Most security bugs are caused by attackers playing around with symlinks
+// trying to escape from the container onto the host and/or trick into data
+// corruption on the host. Hence, file operations on containers (including
+// *stat) should always be handled by `github.com/containers/buildah/copier`
+// which makes sure to evaluate files in a chroot'ed environment.
+//
+// Please make sure to add verbose comments when changing code to make the
+// lives of future readers easier.
+//
+// ****************************************************************************
+
+// Copy the source item to destination. Use extract to untar the source if
+// it's a tar archive.
+func Copy(source *CopyItem, destination *CopyItem, extract bool) error {
+ // First, do the man-page dance. See podman-cp(1) for details.
+ if err := enforceCopyRules(source, destination); err != nil {
+ return err
+ }
+
+ // Destination is a stream (e.g., stdout or an http body).
+ if destination.info.IsStream {
+ // Source is a stream (e.g., stdin or an http body).
+ if source.info.IsStream {
+ _, err := io.Copy(destination.writer, source.reader)
+ return err
+ }
+ root, glob, err := source.buildahGlobs()
+ if err != nil {
+ return err
+ }
+ return buildahCopiah.Get(root, "", source.getOptions(), []string{glob}, destination.writer)
+ }
+
+ // Destination is either a file or a directory.
+ if source.info.IsStream {
+ return buildahCopiah.Put(destination.root, destination.resolved, source.putOptions(), source.reader)
+ }
+
+ tarOptions := &archive.TarOptions{
+ Compression: archive.Uncompressed,
+ CopyPass: true,
+ }
+
+ root := destination.root
+ dir := destination.resolved
+ if !source.info.IsDir {
+ // When copying a file, make sure to rename the
+ // destination base path.
+ nameMap := make(map[string]string)
+ nameMap[filepath.Base(source.resolved)] = filepath.Base(destination.resolved)
+ tarOptions.RebaseNames = nameMap
+ dir = filepath.Dir(dir)
+ }
+
+ var tarReader io.ReadCloser
+ if extract && archive.IsArchivePath(source.resolved) {
+ if !destination.info.IsDir {
+ return errors.Errorf("cannot extract archive %q to file %q", source.original, destination.original)
+ }
+
+ reader, err := os.Open(source.resolved)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+
+ // The stream from stdin may be compressed (e.g., via gzip).
+ decompressedStream, err := archive.DecompressStream(reader)
+ if err != nil {
+ return err
+ }
+
+ defer decompressedStream.Close()
+ tarReader = decompressedStream
+ } else {
+ reader, err := archive.TarWithOptions(source.resolved, tarOptions)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+ tarReader = reader
+ }
+
+ return buildahCopiah.Put(root, dir, source.putOptions(), tarReader)
+}
+
+// enforceCopyRules enforces the rules for copying from a source to a
+// destination as mentioned in the podman-cp(1) man page. Please refer to the
+// man page and/or the inline comments for further details. Note that source
+// and destination are passed by reference and the their data may be changed.
+func enforceCopyRules(source, destination *CopyItem) error {
+ if source.statError != nil {
+ return source.statError
+ }
+
+ // We can copy everything to a stream.
+ if destination.info.IsStream {
+ return nil
+ }
+
+ // Source is a *stream*.
+ if source.info.IsStream {
+ if !(destination.info.IsDir || destination.info.IsStream) {
+ return errors.New("destination must be a directory or stream when copying from a stream")
+ }
+ return nil
+ }
+
+ // Source is a *directory*.
+ if source.info.IsDir {
+ if destination.statError != nil {
+ // It's okay if the destination does not exist. We
+ // made sure before that it's parent exists, so it
+ // would be created while copying.
+ if os.IsNotExist(destination.statError) {
+ return nil
+ }
+ // Could be a permission error.
+ return destination.statError
+ }
+
+ // If the destination exists and is not a directory, we have a
+ // problem.
+ if !destination.info.IsDir {
+ return errors.Errorf("cannot copy directory %q to file %q", source.original, destination.original)
+ }
+
+ // If the destination exists and is a directory, we need to
+ // append the source base directory to it. This makes sure
+ // that copying "/foo/bar" "/tmp" will copy to "/tmp/bar" (and
+ // not "/tmp").
+ newDestination, err := securejoin.SecureJoin(destination.resolved, filepath.Base(source.resolved))
+ if err != nil {
+ return err
+ }
+ destination.resolved = newDestination
+ return nil
+ }
+
+ // Source is a *file*.
+ if destination.statError != nil {
+ // It's okay if the destination does not exist, unless it ends
+ // with "/".
+ if !os.IsNotExist(destination.statError) {
+ return destination.statError
+ } else if strings.HasSuffix(destination.resolved, "/") {
+ // Note: this is practically unreachable code as the
+ // existence of parent directories is enforced early
+ // on. It's left here as an extra security net.
+ return errors.Errorf("destination directory %q must exist (trailing %q)", destination.original, "/")
+ }
+ // Does not exist and does not end with "/".
+ return nil
+ }
+
+ // If the destination is a file, we're good. We will overwrite the
+ // contents while copying.
+ if !destination.info.IsDir {
+ return nil
+ }
+
+ // If the destination exists and is a directory, we need to append the
+ // source base directory to it. This makes sure that copying
+ // "/foo/bar" "/tmp" will copy to "/tmp/bar" (and not "/tmp").
+ newDestination, err := securejoin.SecureJoin(destination.resolved, filepath.Base(source.resolved))
+ if err != nil {
+ return err
+ }
+
+ destination.resolved = newDestination
+ return nil
+}
diff --git a/pkg/copy/item.go b/pkg/copy/item.go
new file mode 100644
index 000000000..db6bca610
--- /dev/null
+++ b/pkg/copy/item.go
@@ -0,0 +1,601 @@
+package copy
+
+import (
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ buildahCopiah "github.com/containers/buildah/copier"
+ "github.com/containers/buildah/pkg/chrootuser"
+ "github.com/containers/buildah/util"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/cgroups"
+ "github.com/containers/podman/v2/pkg/rootless"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/archive"
+ "github.com/containers/storage/pkg/idtools"
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// ********************************* NOTE *************************************
+//
+// Most security bugs are caused by attackers playing around with symlinks
+// trying to escape from the container onto the host and/or trick into data
+// corruption on the host. Hence, file operations on containers (including
+// *stat) should always be handled by `github.com/containers/buildah/copier`
+// which makes sure to evaluate files in a chroot'ed environment.
+//
+// Please make sure to add verbose comments when changing code to make the
+// lives of future readers easier.
+//
+// ****************************************************************************
+
+var (
+ _stdin = os.Stdin.Name()
+ _stdout = os.Stdout.Name()
+)
+
+// CopyItem is the source or destination of a copy operation. Use the
+// CopyItemFrom* functions to create one for the specific source/destination
+// item.
+type CopyItem struct {
+ // The original path provided by the caller. Useful in error messages.
+ original string
+ // The resolved path on the host or container. Maybe altered at
+ // multiple stages when copying.
+ resolved string
+ // The root for copying data in a chroot'ed environment.
+ root string
+
+ // IDPair of the resolved path.
+ idPair *idtools.IDPair
+ // Storage ID mappings.
+ idMappings *storage.IDMappingOptions
+
+ // Internal FileInfo. We really don't want users to mess with a
+ // CopyItem but only plug and play with it.
+ info FileInfo
+ // Error when creating the upper FileInfo. Some errors are non-fatal,
+ // for instance, when a destination *base* path does not exist.
+ statError error
+
+ writer io.Writer
+ reader io.Reader
+
+ // Needed to clean up resources (e.g., unmount a container).
+ cleanUpFuncs []deferFunc
+}
+
+// deferFunc allows for returning functions that must be deferred at call sites.
+type deferFunc func()
+
+// FileInfo describes a file or directory and is returned by
+// (*CopyItem).Stat().
+type FileInfo struct {
+ Name string `json:"name"`
+ Size int64 `json:"size"`
+ Mode os.FileMode `json:"mode"`
+ ModTime time.Time `json:"mtime"`
+ IsDir bool `json:"isDir"`
+ IsStream bool `json:"isStream"`
+ LinkTarget string `json:"linkTarget"`
+}
+
+// Stat returns the FileInfo.
+func (item *CopyItem) Stat() (*FileInfo, error) {
+ return &item.info, item.statError
+}
+
+// CleanUp releases resources such as the container mounts. It *must* be
+// called even in case of errors.
+func (item *CopyItem) CleanUp() {
+ for _, f := range item.cleanUpFuncs {
+ f()
+ }
+}
+
+// CopyItemForWriter returns a CopyItem for the specified io.WriteCloser. Note
+// that the returned item can only act as a copy destination.
+func CopyItemForWriter(writer io.Writer) (item CopyItem, _ error) {
+ item.writer = writer
+ item.info.IsStream = true
+ return item, nil
+}
+
+// CopyItemForReader returns a CopyItem for the specified io.ReaderCloser. Note
+// that the returned item can only act as a copy source.
+//
+// Note that the specified reader will be auto-decompressed if needed.
+func CopyItemForReader(reader io.Reader) (item CopyItem, _ error) {
+ item.info.IsStream = true
+ decompressed, err := archive.DecompressStream(reader)
+ if err != nil {
+ return item, err
+ }
+ item.reader = decompressed
+ item.cleanUpFuncs = append(item.cleanUpFuncs, func() {
+ if err := decompressed.Close(); err != nil {
+ logrus.Errorf("Error closing decompressed reader of copy item: %v", err)
+ }
+ })
+ return item, nil
+}
+
+// CopyItemForHost creates a CopyItem for the specified host path. It's a
+// destination by default. Use isSource to set it as a destination.
+//
+// Note that callers *must* call (CopyItem).CleanUp(), even in case of errors.
+func CopyItemForHost(hostPath string, isSource bool) (item CopyItem, _ error) {
+ if hostPath == "-" {
+ if isSource {
+ hostPath = _stdin
+ } else {
+ hostPath = _stdout
+ }
+ }
+
+ if hostPath == _stdin {
+ return CopyItemForReader(os.Stdin)
+ }
+
+ if hostPath == _stdout {
+ return CopyItemForWriter(os.Stdout)
+ }
+
+ // Now do the dance for the host data.
+ resolvedHostPath, err := filepath.Abs(hostPath)
+ if err != nil {
+ return item, err
+ }
+
+ resolvedHostPath = preserveBasePath(hostPath, resolvedHostPath)
+ item.original = hostPath
+ item.resolved = resolvedHostPath
+ item.root = "/"
+
+ statInfo, statError := os.Stat(resolvedHostPath)
+ item.statError = statError
+
+ // It exists, we're done.
+ if statError == nil {
+ item.info.Name = statInfo.Name()
+ item.info.Size = statInfo.Size()
+ item.info.Mode = statInfo.Mode()
+ item.info.ModTime = statInfo.ModTime()
+ item.info.IsDir = statInfo.IsDir()
+ item.info.LinkTarget = resolvedHostPath
+ return item, nil
+ }
+
+ // The source must exist, but let's try to give some human-friendly
+ // errors.
+ if isSource {
+ if os.IsNotExist(item.statError) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on the host", hostPath)
+ }
+ return item, item.statError // could be a permission error
+ }
+
+ // If we're a destination, we need to make sure that the parent
+ // directory exists.
+ parent := filepath.Dir(resolvedHostPath)
+ if _, err := os.Stat(parent); err != nil {
+ if os.IsNotExist(err) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on the host", parent)
+ }
+ return item, err
+ }
+
+ return item, nil
+}
+
+// CopyItemForContainer creates a CopyItem for the specified path on the
+// container. It's a destination by default. Use isSource to set it as a
+// destination. Note that the container path may resolve to a path outside of
+// the container's mount point if the path hits a volume or mount on the
+// container.
+//
+// Note that callers *must* call (CopyItem).CleanUp(), even in case of errors.
+func CopyItemForContainer(container *libpod.Container, containerPath string, pause bool, isSource bool) (item CopyItem, _ error) {
+ // Mount and pause the container.
+ containerMountPoint, err := item.mountAndPauseContainer(container, pause)
+ if err != nil {
+ return item, err
+ }
+
+ // Make sure that "/" copies the *contents* of the mount point and not
+ // the directory.
+ if containerPath == "/" {
+ containerPath += "/."
+ }
+
+ // Now resolve the container's path. It may hit a volume, it may hit a
+ // bind mount, it may be relative.
+ resolvedRoot, resolvedContainerPath, err := resolveContainerPaths(container, containerMountPoint, containerPath)
+ if err != nil {
+ return item, err
+ }
+ resolvedContainerPath = preserveBasePath(containerPath, resolvedContainerPath)
+
+ idMappings, idPair, err := getIDMappingsAndPair(container, containerMountPoint)
+ if err != nil {
+ return item, err
+ }
+
+ item.original = containerPath
+ item.resolved = resolvedContainerPath
+ item.root = resolvedRoot
+ item.idMappings = idMappings
+ item.idPair = idPair
+
+ statInfo, statError := secureStat(resolvedRoot, resolvedContainerPath)
+ item.statError = statError
+
+ // It exists, we're done.
+ if statError == nil {
+ item.info.IsDir = statInfo.IsDir
+ item.info.Name = filepath.Base(statInfo.Name)
+ item.info.Size = statInfo.Size
+ item.info.Mode = statInfo.Mode
+ item.info.ModTime = statInfo.ModTime
+ item.info.IsDir = statInfo.IsDir
+ item.info.LinkTarget = resolvedContainerPath
+ return item, nil
+ }
+
+ // The source must exist, but let's try to give some human-friendly
+ // errors.
+ if isSource {
+ if os.IsNotExist(statError) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on container %s (resolved to %q)", containerPath, container.ID(), resolvedContainerPath)
+ }
+ return item, item.statError // could be a permission error
+ }
+
+ // If we're a destination, we need to make sure that the parent
+ // directory exists.
+ parent := filepath.Dir(resolvedContainerPath)
+ if _, err := secureStat(resolvedRoot, parent); err != nil {
+ if os.IsNotExist(err) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on container %s (resolved to %q)", containerPath, container.ID(), resolvedContainerPath)
+ }
+ return item, err
+ }
+
+ return item, nil
+}
+
+// putOptions returns PUT options for buildah's copier package.
+func (item *CopyItem) putOptions() buildahCopiah.PutOptions {
+ options := buildahCopiah.PutOptions{}
+ if item.idMappings != nil {
+ options.UIDMap = item.idMappings.UIDMap
+ options.GIDMap = item.idMappings.GIDMap
+ }
+ if item.idPair != nil {
+ options.ChownDirs = item.idPair
+ options.ChownFiles = item.idPair
+ }
+ return options
+}
+
+// getOptions returns GET options for buildah's copier package.
+func (item *CopyItem) getOptions() buildahCopiah.GetOptions {
+ options := buildahCopiah.GetOptions{}
+ if item.idMappings != nil {
+ options.UIDMap = item.idMappings.UIDMap
+ options.GIDMap = item.idMappings.GIDMap
+ }
+ if item.idPair != nil {
+ options.ChownDirs = item.idPair
+ options.ChownFiles = item.idPair
+ }
+ return options
+
+}
+
+// mount and pause the container. Also set the item's cleanUpFuncs. Those
+// *must* be invoked by callers, even in case of errors.
+func (item *CopyItem) mountAndPauseContainer(container *libpod.Container, pause bool) (string, error) {
+ // Make sure to pause and unpause the container. We cannot pause on
+ // cgroupsv1 as rootless user, in which case we turn off pausing.
+ if pause && rootless.IsRootless() {
+ cgroupv2, _ := cgroups.IsCgroup2UnifiedMode()
+ if !cgroupv2 {
+ logrus.Debugf("Cannot pause container for copying as a rootless user on cgroupsv1: default to not pause")
+ pause = false
+ }
+ }
+
+ // Mount and unmount the container.
+ mountPoint, err := container.Mount()
+ if err != nil {
+ return "", err
+ }
+
+ item.cleanUpFuncs = append(item.cleanUpFuncs, func() {
+ if err := container.Unmount(false); err != nil {
+ logrus.Errorf("Error unmounting container after copy operation: %v", err)
+ }
+ })
+
+ // Pause and unpause the container.
+ if pause {
+ if err := container.Pause(); err != nil {
+ // Ignore errors when the container isn't running. No
+ // need to pause.
+ if errors.Cause(err) != define.ErrCtrStateInvalid {
+ return "", err
+ }
+ } else {
+ item.cleanUpFuncs = append(item.cleanUpFuncs, func() {
+ if err := container.Unpause(); err != nil {
+ logrus.Errorf("Error unpausing container after copy operation: %v", err)
+ }
+ })
+ }
+ }
+
+ return mountPoint, nil
+}
+
+// buildahGlobs returns the root, dir and glob used in buildah's copier
+// package.
+//
+// Note that dir is always empty.
+func (item *CopyItem) buildahGlobs() (root string, glob string, err error) {
+ root = item.root
+
+ // If the root and the resolved path are equal, then dir must be empty
+ // and the glob must be ".".
+ if filepath.Clean(root) == filepath.Clean(item.resolved) {
+ glob = "."
+ return
+ }
+
+ glob, err = filepath.Rel(root, item.resolved)
+ return
+}
+
+// preserveBasePath makes sure that the original base path (e.g., "/" or "./")
+// is preserved. The filepath API among tends to clean up a bit too much but
+// we *must* preserve this data by all means.
+func preserveBasePath(original, resolved string) string {
+ // Handle "/"
+ if strings.HasSuffix(original, "/") {
+ if !strings.HasSuffix(resolved, "/") {
+ resolved += "/"
+ }
+ return resolved
+ }
+
+ // Handle "/."
+ if strings.HasSuffix(original, "/.") {
+ if strings.HasSuffix(resolved, "/") { // could be root!
+ resolved += "."
+ } else if !strings.HasSuffix(resolved, "/.") {
+ resolved += "/."
+ }
+ return resolved
+ }
+
+ return resolved
+}
+
+// secureStat extracts file info for path in a chroot'ed environment in root.
+func secureStat(root string, path string) (*buildahCopiah.StatForItem, error) {
+ var glob string
+ var err error
+
+ // If root and path are equal, then dir must be empty and the glob must
+ // be ".".
+ if filepath.Clean(root) == filepath.Clean(path) {
+ glob = "."
+ } else {
+ glob, err = filepath.Rel(root, path)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ globStats, err := buildahCopiah.Stat(root, "", buildahCopiah.StatOptions{}, []string{glob})
+ if err != nil {
+ return nil, err
+ }
+
+ if len(globStats) != 1 {
+ return nil, errors.Errorf("internal libpod error: secureStat: expected 1 item but got %d", len(globStats))
+ }
+
+ stat, exists := globStats[0].Results[glob] // only one glob passed, so that's okay
+ if !exists {
+ return stat, os.ErrNotExist
+ }
+
+ var statErr error
+ if stat.Error != "" {
+ statErr = errors.New(stat.Error)
+ }
+ return stat, statErr
+}
+
+// resolveContainerPaths resolves the container's mount point and the container
+// path as specified by the user. Both may resolve to paths outside of the
+// container's mount point when the container path hits a volume or bind mount.
+//
+// NOTE: We must take volumes and bind mounts into account as, regrettably, we
+// can copy to/from stopped containers. In that case, the volumes and bind
+// mounts are not present. For running containers, the runtime (e.g., runc or
+// crun) takes care of these mounts. For stopped ones, we need to do quite
+// some dance, as done below.
+func resolveContainerPaths(container *libpod.Container, mountPoint string, containerPath string) (string, string, error) {
+ // Let's first make sure we have a path relative to the mount point.
+ pathRelativeToContainerMountPoint := containerPath
+ if !filepath.IsAbs(containerPath) {
+ // If the containerPath is not absolute, it's relative to the
+ // container's working dir. To be extra careful, let's first
+ // join the working dir with "/", and the add the containerPath
+ // to it.
+ pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath)
+ }
+ // NOTE: the secure join makes sure that we follow symlinks. This way,
+ // we catch scenarios where the container path symlinks to a volume or
+ // bind mount.
+ resolvedPathOnTheContainerMountPoint, err := securejoin.SecureJoin(mountPoint, pathRelativeToContainerMountPoint)
+ if err != nil {
+ return "", "", err
+ }
+ pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint)
+ pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint)
+
+ // Now we have an "absolute container Path" but not yet resolved on the
+ // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to
+ // check if "/foo/bar/file.txt" is on a volume or bind mount. To do
+ // that, we need to walk *down* the paths to the root. Assuming
+ // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar",
+ // we must select "/foo/bar". Once selected, we need to rebase the
+ // remainder (i.e, "/file.txt") on the volume's mount point on the
+ // host. Same applies to bind mounts.
+
+ searchPath := pathRelativeToContainerMountPoint
+ for {
+ volume, err := findVolume(container, searchPath)
+ if err != nil {
+ return "", "", err
+ }
+ if volume != nil {
+ logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
+ // We found a matching volume for searchPath. We now
+ // need to first find the relative path of our input
+ // path to the searchPath, and then join it with the
+ // volume's mount point.
+ pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
+ absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(volume.MountPoint(), pathRelativeToVolume)
+ if err != nil {
+ return "", "", err
+ }
+ return volume.MountPoint(), absolutePathOnTheVolumeMount, nil
+ }
+
+ if mount := findBindMount(container, searchPath); mount != nil {
+ logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath)
+ // We found a matching bind mount for searchPath. We
+ // now need to first find the relative path of our
+ // input path to the searchPath, and then join it with
+ // the source of the bind mount.
+ pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
+ absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
+ if err != nil {
+ return "", "", err
+ }
+ return mount.Source, absolutePathOnTheBindMount, nil
+
+ }
+
+ if searchPath == "/" {
+ // Cannot go beyond "/", so we're done.
+ break
+ }
+
+ // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar").
+ searchPath = filepath.Dir(searchPath)
+ }
+
+ // No volume, no bind mount but just a normal path on the container.
+ return mountPoint, resolvedPathOnTheContainerMountPoint, nil
+}
+
+// findVolume checks if the specified container path matches a volume inside
+// the container. It returns a matching volume or nil.
+func findVolume(c *libpod.Container, containerPath string) (*libpod.Volume, error) {
+ runtime := c.Runtime()
+ cleanedContainerPath := filepath.Clean(containerPath)
+ for _, vol := range c.Config().NamedVolumes {
+ if cleanedContainerPath == filepath.Clean(vol.Dest) {
+ return runtime.GetVolume(vol.Name)
+ }
+ }
+ return nil, nil
+}
+
+// findBindMount checks if the specified container path matches a bind mount
+// inside the container. It returns a matching mount or nil.
+func findBindMount(c *libpod.Container, containerPath string) *specs.Mount {
+ cleanedPath := filepath.Clean(containerPath)
+ for _, m := range c.Config().Spec.Mounts {
+ if m.Type != "bind" {
+ continue
+ }
+ if cleanedPath == filepath.Clean(m.Destination) {
+ mount := m
+ return &mount
+ }
+ }
+ return nil
+}
+
+// getIDMappingsAndPair returns the ID mappings for the container and the host
+// ID pair.
+func getIDMappingsAndPair(container *libpod.Container, containerMount string) (*storage.IDMappingOptions, *idtools.IDPair, error) {
+ user, err := getContainerUser(container, containerMount)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ idMappingOpts, err := container.IDMappings()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ hostUID, hostGID, err := util.GetHostIDs(idtoolsToRuntimeSpec(idMappingOpts.UIDMap), idtoolsToRuntimeSpec(idMappingOpts.GIDMap), user.UID, user.GID)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
+ return &idMappingOpts, &idPair, nil
+}
+
+// getContainerUser returns the specs.User of the container.
+func getContainerUser(container *libpod.Container, mountPoint string) (specs.User, error) {
+ userspec := container.Config().User
+
+ uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
+ u := specs.User{
+ UID: uid,
+ GID: gid,
+ Username: userspec,
+ }
+
+ if !strings.Contains(userspec, ":") {
+ groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
+ if err2 != nil {
+ if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
+ err = err2
+ }
+ } else {
+ u.AdditionalGids = groups
+ }
+ }
+
+ return u, err
+}
+
+// idtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec.
+func idtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
+ for _, idmap := range idMaps {
+ tempIDMap := specs.LinuxIDMapping{
+ ContainerID: uint32(idmap.ContainerID),
+ HostID: uint32(idmap.HostID),
+ Size: uint32(idmap.Size),
+ }
+ convertedIDMap = append(convertedIDMap, tempIDMap)
+ }
+ return convertedIDMap
+}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 3fd7c79f4..39d679eaf 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -403,16 +403,14 @@ type ContainerPortReport struct {
Ports []ocicni.PortMapping
}
-// ContainerCpOptions describes input options for cp
+// ContainerCpOptions describes input options for cp.
type ContainerCpOptions struct {
- Pause bool
+ // Pause the container while copying.
+ Pause bool
+ // Extract the tarfile into the destination directory.
Extract bool
}
-// ContainerCpReport describes the output from a cp operation
-type ContainerCpReport struct {
-}
-
// ContainerStatsOptions describes input options for getting
// stats on containers
type ContainerStatsOptions struct {
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index df7da616a..e1f40e307 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -16,7 +16,7 @@ type ContainerEngine interface {
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error)
ContainerCommit(ctx context.Context, nameOrID string, options CommitOptions) (*CommitReport, error)
- ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error)
+ ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) error
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerDiff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error)
ContainerExec(ctx context.Context, nameOrID string, options ExecOptions, streams define.AttachStreams) (int, error)
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index ab545d882..81f12bff7 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -51,22 +51,22 @@ func (i *Image) Id() string { // nolint
}
type ImageSummary struct {
- ID string `json:"Id"`
- ParentId string // nolint
- RepoTags []string `json:",omitempty"`
+ ID string `json:"Id"`
+ ParentId string // nolint
+ RepoTags []string
+ RepoDigests []string
Created int64
- Size int64 `json:",omitempty"`
- SharedSize int `json:",omitempty"`
- VirtualSize int64 `json:",omitempty"`
- Labels map[string]string `json:",omitempty"`
- Containers int `json:",omitempty"`
- ReadOnly bool `json:",omitempty"`
- Dangling bool `json:",omitempty"`
+ Size int64
+ SharedSize int
+ VirtualSize int64
+ Labels map[string]string
+ Containers int
+ ReadOnly bool `json:",omitempty"`
+ Dangling bool `json:",omitempty"`
// Podman extensions
Names []string `json:",omitempty"`
Digest string `json:",omitempty"`
- Digests []string `json:",omitempty"`
ConfigDigest string `json:",omitempty"`
History []string `json:",omitempty"`
}
diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go
index 86c2e1bcd..65a110fd9 100644
--- a/pkg/domain/entities/network.go
+++ b/pkg/domain/entities/network.go
@@ -8,14 +8,15 @@ import (
// NetworkListOptions describes options for listing networks in cli
type NetworkListOptions struct {
- Format string
- Quiet bool
- Filter string
+ Format string
+ Quiet bool
+ Filters map[string][]string
}
// NetworkListReport describes the results from listing networks
type NetworkListReport struct {
*libcni.NetworkConfigList
+ Labels map[string]string
}
// NetworkInspectReport describes the results from inspect networks
@@ -39,10 +40,13 @@ type NetworkCreateOptions struct {
Driver string
Gateway net.IP
Internal bool
+ Labels map[string]string
MacVLAN string
Range net.IPNet
Subnet net.IPNet
IPv6 bool
+ // Mapping of driver options and values.
+ Options map[string]string
}
// NetworkCreateReport describes a created network for the cli
diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go
index 8f4f5d3d7..9409df743 100644
--- a/pkg/domain/infra/abi/cp.go
+++ b/pkg/domain/infra/abi/cp.go
@@ -1,195 +1,70 @@
package abi
import (
- "archive/tar"
"context"
- "fmt"
- "io"
- "os"
- "path/filepath"
"strings"
- "github.com/containers/buildah/pkg/chrootuser"
- "github.com/containers/buildah/util"
"github.com/containers/podman/v2/libpod"
- "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/copy"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/storage"
- "github.com/containers/storage/pkg/chrootarchive"
- "github.com/containers/storage/pkg/idtools"
- securejoin "github.com/cyphar/filepath-securejoin"
- "github.com/docker/docker/pkg/archive"
- "github.com/opencontainers/go-digest"
- "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
-func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
- extract := options.Extract
-
+func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) error {
srcCtr, srcPath := parsePath(ic.Libpod, source)
destCtr, destPath := parsePath(ic.Libpod, dest)
- if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
- return nil, errors.Errorf("invalid arguments %s, %s you must use just one container", source, dest)
+ if srcCtr != nil && destCtr != nil {
+ return errors.Errorf("invalid arguments %q, %q: you must use just one container", source, dest)
}
-
- if len(srcPath) == 0 || len(destPath) == 0 {
- return nil, errors.Errorf("invalid arguments %s, %s you must specify paths", source, dest)
+ if srcCtr == nil && destCtr == nil {
+ return errors.Errorf("invalid arguments %q, %q: you must specify one container", source, dest)
}
- ctr := srcCtr
- isFromHostToCtr := ctr == nil
- if isFromHostToCtr {
- ctr = destCtr
+ if len(srcPath) == 0 || len(destPath) == 0 {
+ return errors.Errorf("invalid arguments %q, %q: you must specify paths", source, dest)
}
- mountPoint, err := ctr.Mount()
- if err != nil {
- return nil, err
- }
- defer func() {
- if err := ctr.Unmount(false); err != nil {
- logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err)
+ var sourceItem, destinationItem copy.CopyItem
+ var err error
+ // Copy from the container to the host.
+ if srcCtr != nil {
+ sourceItem, err = copy.CopyItemForContainer(srcCtr, srcPath, options.Pause, true)
+ defer sourceItem.CleanUp()
+ if err != nil {
+ return err
}
- }()
-
- if options.Pause {
- if err := ctr.Pause(); err != nil {
- // An invalid state error is fine.
- // The container isn't running or is already paused.
- // TODO: We can potentially start the container while
- // the copy is running, which still allows a race where
- // malicious code could mess with the symlink.
- if errors.Cause(err) != define.ErrCtrStateInvalid {
- return nil, err
- }
- } else {
- // Only add the defer if we actually paused
- defer func() {
- if err := ctr.Unpause(); err != nil {
- logrus.Errorf("Error unpausing container after copying: %v", err)
- }
- }()
+ } else {
+ sourceItem, err = copy.CopyItemForHost(srcPath, true)
+ if err != nil {
+ return err
}
}
- user, err := getUser(mountPoint, ctr.User())
- if err != nil {
- return nil, err
- }
- idMappingOpts, err := ctr.IDMappings()
- if err != nil {
- return nil, errors.Wrapf(err, "error getting IDMappingOptions")
- }
- destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
- hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID)
- if err != nil {
- return nil, err
- }
-
- hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
-
- if isFromHostToCtr {
- if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic)
- path, err := pathWithVolumeMount(ic.Libpod, volDestName, volName, destPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting destination path from volume %s", volDestName)
- }
- destPath = path
- } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic)
- path, err := pathWithBindMountSource(mount, destPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination)
- }
- destPath = path
- } else if filepath.IsAbs(destPath) { //nolint(gocritic)
- cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath)
- if err != nil {
- return nil, err
- }
- destPath = cleanedPath
- } else { //nolint(gocritic)
- ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir())
- if err != nil {
- return nil, err
- }
- if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil {
- return nil, err
- }
- cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath))
- if err != nil {
- return nil, err
- }
- destPath = cleanedPath
+ if destCtr != nil {
+ destinationItem, err = copy.CopyItemForContainer(destCtr, destPath, options.Pause, false)
+ defer destinationItem.CleanUp()
+ if err != nil {
+ return err
}
} else {
- destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
- if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic)
- path, err := pathWithVolumeMount(ic.Libpod, volDestName, volName, srcPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting source path from volume %s", volDestName)
- }
- srcPath = path
- } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic)
- path, err := pathWithBindMountSource(mount, srcPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination)
- }
- srcPath = path
- } else if filepath.IsAbs(srcPath) { //nolint(gocritic)
- cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath)
- if err != nil {
- return nil, err
- }
- srcPath = cleanedPath
- } else { //nolint(gocritic)
- cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath))
- if err != nil {
- return nil, err
- }
- srcPath = cleanedPath
- }
- }
-
- if !filepath.IsAbs(destPath) {
- dir, err := os.Getwd()
+ destinationItem, err = copy.CopyItemForHost(destPath, false)
+ defer destinationItem.CleanUp()
if err != nil {
- return nil, errors.Wrapf(err, "err getting current working directory")
+ return err
}
- destPath = filepath.Join(dir, destPath)
}
- if source == "-" {
- srcPath = os.Stdin.Name()
- extract = true
- }
- err = containerCopy(srcPath, destPath, source, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr)
- return &entities.ContainerCpReport{}, err
+ // Copy from the host to the container.
+ return copy.Copy(&sourceItem, &destinationItem, options.Extract)
}
-func getUser(mountPoint string, userspec string) (specs.User, error) {
- uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
- u := specs.User{
- UID: uid,
- GID: gid,
- Username: userspec,
+func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) {
+ if len(path) == 0 {
+ return nil, ""
}
- if !strings.Contains(userspec, ":") {
- groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
- if err2 != nil {
- if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
- err = err2
- }
- } else {
- u.AdditionalGids = groups
- }
-
+ if path[0] == '.' || path[0] == '/' { // A path cannot point to a container.
+ return nil, path
}
- return u, err
-}
-
-func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) {
pathArr := strings.SplitN(path, ":", 2)
if len(pathArr) == 2 {
ctr, err := runtime.LookupContainer(pathArr[0])
@@ -199,247 +74,3 @@ func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string)
}
return nil, path
}
-
-func evalSymlinks(path string) (string, error) {
- if path == os.Stdin.Name() {
- return path, nil
- }
- return filepath.EvalSymlinks(path)
-}
-
-func getPathInfo(path string) (string, os.FileInfo, error) {
- path, err := evalSymlinks(path)
- if err != nil {
- return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path)
- }
- srcfi, err := os.Stat(path)
- if err != nil {
- return "", nil, err
- }
- return path, srcfi, nil
-}
-
-func containerCopy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error {
- srcPath, err := evalSymlinks(srcPath)
- if err != nil {
- return errors.Wrapf(err, "error evaluating symlinks %q", srcPath)
- }
-
- srcPath, srcfi, err := getPathInfo(srcPath)
- if err != nil {
- return err
- }
-
- filename := filepath.Base(destPath)
- if filename == "-" && !isFromHostToCtr {
- err := streamFileToStdout(srcPath, srcfi)
- if err != nil {
- return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath)
- }
- return nil
- }
-
- destdir := destPath
- if !srcfi.IsDir() {
- destdir = filepath.Dir(destPath)
- }
- _, err = os.Stat(destdir)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- destDirIsExist := err == nil
- if err = os.MkdirAll(destdir, 0755); err != nil {
- return err
- }
-
- // return functions for copying items
- copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
- copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
- untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
-
- if srcfi.IsDir() {
- logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*")
- if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) {
- srcPathBase := filepath.Base(srcPath)
- if !isFromHostToCtr {
- pathArr := strings.SplitN(src, ":", 2)
- if len(pathArr) != 2 {
- return errors.Errorf("invalid arguments %s, you must specify source path", src)
- }
- if pathArr[1] == "/" {
- // If `srcPath` is the root directory of the container,
- // `srcPath` will be `.../${sha256_ID}/merged/`, so do not join it
- srcPathBase = ""
- }
- }
- destPath = filepath.Join(destPath, srcPathBase)
- }
- if err = copyWithTar(srcPath, destPath); err != nil {
- return errors.Wrapf(err, "error copying %q to %q", srcPath, dest)
- }
- return nil
- }
-
- if extract {
- // We're extracting an archive into the destination directory.
- logrus.Debugf("extracting contents of %q into %q", srcPath, destPath)
- if err = untarPath(srcPath, destPath); err != nil {
- return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath)
- }
- return nil
- }
-
- destfi, err := os.Stat(destPath)
- if err != nil {
- if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) {
- return err
- }
- }
- if destfi != nil && destfi.IsDir() {
- destPath = filepath.Join(destPath, filepath.Base(srcPath))
- }
-
- // Copy the file, preserving attributes.
- logrus.Debugf("copying %q to %q", srcPath, destPath)
- if err = copyFileWithTar(srcPath, destPath); err != nil {
- return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath)
- }
- return nil
-}
-
-func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
- for _, idmap := range idMaps {
- tempIDMap := specs.LinuxIDMapping{
- ContainerID: uint32(idmap.ContainerID),
- HostID: uint32(idmap.HostID),
- Size: uint32(idmap.Size),
- }
- convertedIDMap = append(convertedIDMap, tempIDMap)
- }
- return convertedIDMap
-}
-
-func streamFileToStdout(srcPath string, srcfi os.FileInfo) error {
- if srcfi.IsDir() {
- tw := tar.NewWriter(os.Stdout)
- err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
- if err != nil || !info.Mode().IsRegular() || path == srcPath {
- return err
- }
- hdr, err := tar.FileInfoHeader(info, "")
- if err != nil {
- return err
- }
-
- if err = tw.WriteHeader(hdr); err != nil {
- return err
- }
- fh, err := os.Open(path)
- if err != nil {
- return err
- }
- defer fh.Close()
-
- _, err = io.Copy(tw, fh)
- return err
- })
- if err != nil {
- return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath)
- }
- return nil
- }
-
- file, err := os.Open(srcPath)
- if err != nil {
- return err
- }
- defer file.Close()
- if !archive.IsArchivePath(srcPath) {
- tw := tar.NewWriter(os.Stdout)
- hdr, err := tar.FileInfoHeader(srcfi, "")
- if err != nil {
- return err
- }
- err = tw.WriteHeader(hdr)
- if err != nil {
- return err
- }
- _, err = io.Copy(tw, file)
- if err != nil {
- return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath)
- }
- return nil
- }
-
- _, err = io.Copy(os.Stdout, file)
- if err != nil {
- return errors.Wrapf(err, "error streaming file to Stdout")
- }
- return nil
-}
-
-func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
- separator := string(os.PathSeparator)
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, separator)
- }
- if path == "" {
- return false, "", ""
- }
- for _, vol := range ctr.Config().NamedVolumes {
- volNamePath := strings.TrimPrefix(vol.Dest, separator)
- if matchVolumePath(path, volNamePath) {
- return true, vol.Dest, vol.Name
- }
- }
- return false, "", ""
-}
-
-// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point
-func pathWithVolumeMount(runtime *libpod.Runtime, volDestName, volName, path string) (string, error) {
- destVolume, err := runtime.GetVolume(volName)
- if err != nil {
- return "", errors.Wrapf(err, "error getting volume destination %s", volName)
- }
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
- }
- path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName))
- return path, err
-}
-
-func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
- separator := string(os.PathSeparator)
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, string(os.PathSeparator))
- }
- if path == "" {
- return false, specs.Mount{}
- }
- for _, m := range ctr.Config().Spec.Mounts {
- if m.Type != "bind" {
- continue
- }
- mDest := strings.TrimPrefix(m.Destination, separator)
- if matchVolumePath(path, mDest) {
- return true, m
- }
- }
- return false, specs.Mount{}
-}
-
-func matchVolumePath(path, target string) bool {
- pathStr := filepath.Clean(path)
- target = filepath.Clean(target)
- for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
- pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
- }
- return pathStr == target
-}
-
-func pathWithBindMountSource(m specs.Mount, path string) (string, error) {
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
- }
- return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination))
-}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index ef0e15264..ff2f2e7ae 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -458,7 +458,7 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions)
if !opts.Quiet {
writer = os.Stderr
}
- name, err := ir.Libpod.LoadImage(ctx, opts.Name, opts.Input, writer, opts.SignaturePolicy)
+ name, err := ir.Libpod.LoadImage(ctx, opts.Input, writer, opts.SignaturePolicy)
if err != nil {
return nil, err
}
@@ -714,83 +714,90 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
}
for _, signimage := range names {
- srcRef, err := alltransports.ParseImageName(signimage)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing image name")
- }
- rawSource, err := srcRef.NewImageSource(ctx, sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting image source")
- }
- err = rawSource.Close()
- if err != nil {
- logrus.Errorf("unable to close new image source %q", err)
- }
- getManifest, _, err := rawSource.GetManifest(ctx, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting getManifest")
- }
- dockerReference := rawSource.Reference().DockerReference()
- if dockerReference == nil {
- return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
- }
- var sigStoreDir string
- if options.Directory != "" {
- sigStoreDir = options.Directory
- }
- if sigStoreDir == "" {
- if rootless.IsRootless() {
- sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore")
- } else {
- var sigStoreURI string
- registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs)
- if registryInfo != nil {
- if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" {
- sigStoreURI = registryInfo.SigStore
- }
+ err = func() error {
+ srcRef, err := alltransports.ParseImageName(signimage)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing image name")
+ }
+ rawSource, err := srcRef.NewImageSource(ctx, sc)
+ if err != nil {
+ return errors.Wrapf(err, "error getting image source")
+ }
+ defer func() {
+ if err = rawSource.Close(); err != nil {
+ logrus.Errorf("unable to close %s image source %q", srcRef.DockerReference().Name(), err)
}
- if sigStoreURI == "" {
- return nil, errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String())
+ }()
+ getManifest, _, err := rawSource.GetManifest(ctx, nil)
+ if err != nil {
+ return errors.Wrapf(err, "error getting getManifest")
+ }
+ dockerReference := rawSource.Reference().DockerReference()
+ if dockerReference == nil {
+ return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
+ }
+ var sigStoreDir string
+ if options.Directory != "" {
+ sigStoreDir = options.Directory
+ }
+ if sigStoreDir == "" {
+ if rootless.IsRootless() {
+ sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore")
+ } else {
+ var sigStoreURI string
+ registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs)
+ if registryInfo != nil {
+ if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" {
+ sigStoreURI = registryInfo.SigStore
+ }
+ }
+ if sigStoreURI == "" {
+ return errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String())
- }
- sigStoreDir, err = localPathFromURI(sigStoreURI)
- if err != nil {
- return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreURI)
+ }
+ sigStoreDir, err = localPathFromURI(sigStoreURI)
+ if err != nil {
+ return errors.Wrapf(err, "invalid signature storage %s", sigStoreURI)
+ }
}
}
- }
- manifestDigest, err := manifest.Digest(getManifest)
- if err != nil {
- return nil, err
- }
- repo := reference.Path(dockerReference)
- if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
- return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String())
- }
+ manifestDigest, err := manifest.Digest(getManifest)
+ if err != nil {
+ return err
+ }
+ repo := reference.Path(dockerReference)
+ if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
+ return errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String())
+ }
- // create signature
- newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
- if err != nil {
- return nil, errors.Wrapf(err, "error creating new signature")
- }
- // create the signstore file
- signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex())
- if err := os.MkdirAll(signatureDir, 0751); err != nil {
- // The directory is allowed to exist
- if !os.IsExist(err) {
- logrus.Error(err)
- continue
+ // create signature
+ newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
+ if err != nil {
+ return errors.Wrapf(err, "error creating new signature")
}
- }
- sigFilename, err := getSigFilename(signatureDir)
- if err != nil {
- logrus.Errorf("error creating sigstore file: %v", err)
- continue
- }
- err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644)
+ // create the signstore file
+ signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex())
+ if err := os.MkdirAll(signatureDir, 0751); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ logrus.Error(err)
+ return nil
+ }
+ }
+ sigFilename, err := getSigFilename(signatureDir)
+ if err != nil {
+ logrus.Errorf("error creating sigstore file: %v", err)
+ return nil
+ }
+ err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644)
+ if err != nil {
+ logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
+ return nil
+ }
+ return nil
+ }()
if err != nil {
- logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
- continue
+ return nil, err
}
}
return nil, nil
diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go
index 281b04294..c4b0b7712 100644
--- a/pkg/domain/infra/abi/images_list.go
+++ b/pkg/domain/infra/abi/images_list.go
@@ -35,13 +35,11 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
Created: img.Created().Unix(),
Dangling: img.Dangling(),
Digest: string(img.Digest()),
- Digests: digests,
+ RepoDigests: digests,
History: img.NamesHistory(),
Names: img.Names(),
- ParentId: img.Parent,
ReadOnly: img.IsReadOnly(),
SharedSize: 0,
- VirtualSize: img.VirtualSize,
RepoTags: img.Names(), // may include tags and digests
}
e.Labels, err = img.Labels(ctx)
@@ -60,6 +58,15 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
return nil, errors.Wrapf(err, "error retrieving size of image %q: you may need to remove the image to resolve the error", img.ID())
}
e.Size = int64(*sz)
+ // This is good enough for now, but has to be
+ // replaced later with correct calculation logic
+ e.VirtualSize = int64(*sz)
+
+ parent, err := img.ParentID(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving parent of image %q: you may need to remove the image to resolve the error", img.ID())
+ }
+ e.ParentId = parent
summaries = append(summaries, &e)
}
diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go
index c52584565..6a219edd5 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -2,10 +2,7 @@ package abi
import (
"context"
- "fmt"
- "strings"
- "github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -26,18 +23,16 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net
return nil, err
}
- var tokens []string
- // tokenize the networkListOptions.Filter in key=value.
- if len(options.Filter) > 0 {
- tokens = strings.Split(options.Filter, "=")
- if len(tokens) != 2 {
- return nil, fmt.Errorf("invalid filter syntax : %s", options.Filter)
- }
- }
-
for _, n := range networks {
- if ifPassesFilterTest(n, tokens) {
- reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n})
+ ok, err := network.IfPassesFilter(n, options.Filters)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ reports = append(reports, &entities.NetworkListReport{
+ NetworkConfigList: n,
+ Labels: network.GetNetworkLabels(n),
+ })
}
}
return reports, nil
@@ -117,28 +112,6 @@ func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, optio
return network.Create(name, options, runtimeConfig)
}
-func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool {
- result := false
- if len(filter) == 0 {
- // No filter, so pass
- return true
- }
- switch strings.ToLower(filter[0]) {
- case "name":
- if filter[1] == netconf.Name {
- result = true
- }
- case "plugin":
- plugins := network.GetCNIPlugins(netconf)
- if strings.Contains(plugins, filter[1]) {
- result = true
- }
- default:
- result = false
- }
- return result
-}
-
// NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force)
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 72fd98ac1..ec2532bea 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -11,6 +11,7 @@ import (
"strings"
"github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/cgroups"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -86,7 +87,11 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
return nil
}
- pausePidPath, err := util.GetRootlessPauseProcessPidPath()
+ tmpDir, err := ic.Libpod.TmpDir()
+ if err != nil {
+ return err
+ }
+ pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
@@ -112,7 +117,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
}
became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths)
- if err := movePauseProcessToScope(); err != nil {
+ if err := movePauseProcessToScope(ic.Libpod); err != nil {
conf, err := ic.Config(context.Background())
if err != nil {
return err
@@ -133,8 +138,12 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
return nil
}
-func movePauseProcessToScope() error {
- pausePidPath, err := util.GetRootlessPauseProcessPidPath()
+func movePauseProcessToScope(r *libpod.Runtime) error {
+ tmpDir, err := r.TmpDir()
+ if err != nil {
+ return err
+ }
+ pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 63677719b..3584668c7 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -731,8 +731,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o
return reports, nil
}
-func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
- return nil, errors.New("not implemented")
+func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) error {
+ return errors.New("not implemented")
}
// Shutdown Libpod engine
diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go
index 2d40dba8f..1808f99b8 100644
--- a/pkg/specgen/generate/config_linux.go
+++ b/pkg/specgen/generate/config_linux.go
@@ -4,13 +4,16 @@ import (
"fmt"
"io/ioutil"
"os"
+ "path"
"path/filepath"
"strings"
"github.com/containers/podman/v2/pkg/rootless"
+ "github.com/containers/podman/v2/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@@ -137,22 +140,33 @@ func DevicesFromPath(g *generate.Generator, devicePath string) error {
return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":"))
}
-func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) {
+func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask []string, g *generate.Generator) {
+ defaultMaskPaths := []string{"/proc/acpi",
+ "/proc/kcore",
+ "/proc/keys",
+ "/proc/latency_stats",
+ "/proc/timer_list",
+ "/proc/timer_stats",
+ "/proc/sched_debug",
+ "/proc/scsi",
+ "/sys/firmware",
+ "/sys/fs/selinux",
+ "/sys/dev/block",
+ }
+
+ unmaskAll := false
+ if unmask != nil && unmask[0] == "ALL" {
+ unmaskAll = true
+ }
+
if !privileged {
- for _, mp := range []string{
- "/proc/acpi",
- "/proc/kcore",
- "/proc/keys",
- "/proc/latency_stats",
- "/proc/timer_list",
- "/proc/timer_stats",
- "/proc/sched_debug",
- "/proc/scsi",
- "/sys/firmware",
- "/sys/fs/selinux",
- "/sys/dev",
- } {
- g.AddLinuxMaskedPaths(mp)
+ if !unmaskAll {
+ for _, mp := range defaultMaskPaths {
+ // check that the path to mask is not in the list of paths to unmask
+ if !util.StringInSlice(mp, unmask) {
+ g.AddLinuxMaskedPaths(mp)
+ }
+ }
}
if pidModeIsHost && rootless.IsRootless() {
@@ -170,6 +184,15 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.
g.AddLinuxReadonlyPaths(rp)
}
}
+
+ // mask the paths provided by the user
+ for _, mp := range mask {
+ if !path.IsAbs(mp) && mp != "" {
+ logrus.Errorf("Path %q is not an absolute path, skipping...", mp)
+ continue
+ }
+ g.AddLinuxMaskedPaths(mp)
+ }
}
// based on getDevices from runc (libcontainer/devices/devices.go)
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 45a374216..4f36744ca 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -98,7 +98,6 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
// present.
imgName := newImage.InputName
if s.Image == newImage.InputName && strings.HasPrefix(newImage.ID(), s.Image) {
- imgName = ""
names := newImage.Names()
if len(names) > 0 {
imgName = names[0]
@@ -388,7 +387,7 @@ func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Co
}
if syslog {
- command = append(command, "--syslog", "true")
+ command = append(command, "--syslog")
}
command = append(command, []string{"container", "cleanup"}...)
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index ddc73ca61..036c7b7a1 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -233,6 +233,8 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
}
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
+ case specgen.Private:
+ fallthrough
case specgen.Bridge:
portMappings, err := createPortMappings(ctx, s, img)
if err != nil {
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 9649873fd..c24dcf4c0 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -298,7 +298,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
}
}
- BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g)
+ BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g)
for name, val := range s.Env {
g.AddProcessEnv(name, val)
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index d15745fa0..9d78a0210 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -258,24 +258,22 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
var cniNetworks []string
// Net defaults to Slirp on rootless
switch {
- case ns == "slirp4netns", strings.HasPrefix(ns, "slirp4netns:"):
+ case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
toReturn.NSMode = Slirp
- case ns == "pod":
+ case ns == string(FromPod):
toReturn.NSMode = FromPod
- case ns == "":
+ case ns == "" || ns == string(Default) || ns == string(Private):
if rootless.IsRootless() {
toReturn.NSMode = Slirp
} else {
toReturn.NSMode = Bridge
}
- case ns == "bridge":
+ case ns == string(Bridge):
toReturn.NSMode = Bridge
- case ns == "none":
+ case ns == string(NoNetwork):
toReturn.NSMode = NoNetwork
- case ns == "host":
+ case ns == string(Host):
toReturn.NSMode = Host
- case ns == "private":
- toReturn.NSMode = Private
case strings.HasPrefix(ns, "ns:"):
split := strings.SplitN(ns, ":", 2)
if len(split) != 2 {
@@ -283,7 +281,7 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
}
toReturn.NSMode = Path
toReturn.Value = split[1]
- case strings.HasPrefix(ns, "container:"):
+ case strings.HasPrefix(ns, string(FromContainer)+":"):
split := strings.SplitN(ns, ":", 2)
if len(split) != 2 {
return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:")
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index fad2406e5..964b89fa4 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -307,6 +307,13 @@ type ContainerSecurityConfig struct {
Umask string `json:"umask,omitempty"`
// ProcOpts are the options used for the proc mount.
ProcOpts []string `json:"procfs_opts,omitempty"`
+ // Mask is the path we want to mask in the container. This masks the paths
+ // given in addition to the default list.
+ // Optional
+ Mask []string `json:"mask,omitempty"`
+ // Unmask is the path we want to unmask in the container. To override
+ // all the default paths that are masked, set unmask=ALL.
+ Unmask []string `json:"unmask,omitempty"`
}
// ContainerCgroupConfig contains configuration information about a container's
diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go
index c0acba37d..234a60380 100644
--- a/pkg/systemd/generate/pods.go
+++ b/pkg/systemd/generate/pods.go
@@ -224,7 +224,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
executable, err := os.Executable()
if err != nil {
executable = "/usr/bin/podman"
- logrus.Warnf("Could not obtain podman executable location, using default %s", executable)
+ logrus.Warnf("Could not obtain podman executable location, using default %s: %v", executable, err)
}
info.Executable = executable
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index f6a084c00..e0f631eb4 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -530,6 +530,11 @@ func ParseInputTime(inputTime string) (time.Time, error) {
}
}
+ unix_timestamp, err := strconv.ParseInt(inputTime, 10, 64)
+ if err == nil {
+ return time.Unix(unix_timestamp, 0), nil
+ }
+
// input might be a duration
duration, err := time.ParseDuration(inputTime)
if err != nil {
diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go
index 2d636a7cb..a63c76415 100644
--- a/pkg/util/utils_supported.go
+++ b/pkg/util/utils_supported.go
@@ -99,7 +99,8 @@ func GetRootlessConfigHomeDir() (string, error) {
}
// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for
-// the pause process
+// the pause process.
+// DEPRECATED - switch to GetRootlessPauseProcessPidPathGivenDir
func GetRootlessPauseProcessPidPath() (string, error) {
runtimeDir, err := GetRuntimeDir()
if err != nil {
@@ -107,3 +108,13 @@ func GetRootlessPauseProcessPidPath() (string, error) {
}
return filepath.Join(runtimeDir, "libpod", "pause.pid"), nil
}
+
+// GetRootlessPauseProcessPidPathGivenDir returns the path to the file that
+// holds the PID of the pause process, given the location of Libpod's temporary
+// files.
+func GetRootlessPauseProcessPidPathGivenDir(libpodTmpDir string) (string, error) {
+ if libpodTmpDir == "" {
+ return "", errors.Errorf("must provide non-empty tmporary directory")
+ }
+ return filepath.Join(libpodTmpDir, "pause.pid"), nil
+}
diff --git a/pkg/util/utils_windows.go b/pkg/util/utils_windows.go
index 9bba2d1ee..46ca5e7f1 100644
--- a/pkg/util/utils_windows.go
+++ b/pkg/util/utils_windows.go
@@ -25,6 +25,12 @@ func GetRootlessPauseProcessPidPath() (string, error) {
return "", errors.Wrap(errNotImplemented, "GetRootlessPauseProcessPidPath")
}
+// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for
+// the pause process
+func GetRootlessPauseProcessPidPathGivenDir(unused string) (string, error) {
+ return "", errors.Wrap(errNotImplemented, "GetRootlessPauseProcessPidPath")
+}
+
// GetRuntimeDir returns the runtime directory
func GetRuntimeDir() (string, error) {
return "", errors.New("this function is not implemented for windows")
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index b35c27215..5c35edf2b 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -169,7 +169,7 @@ t GET containers/$cid/json 200 \
.Args[1]="param2"
t DELETE containers/$cid 204
-# test only set the entrpoint, Cmd should be []
+# test only set the entrypoint, Cmd should be []
t POST containers/create '"Image":"'$IMAGE'","Entrypoint":["echo","param1"]' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
diff --git a/test/apiv2/25-containersMore.at b/test/apiv2/25-containersMore.at
index 4f6b80a5f..9d774ef27 100644
--- a/test/apiv2/25-containersMore.at
+++ b/test/apiv2/25-containersMore.at
@@ -79,4 +79,13 @@ like "$output" ".*spec:.*" "Check generated kube yaml(service=true) - spec"
like "$output" ".*kind:\\sService.*" "Check generated kube yaml(service=true) - kind: Service"
t DELETE libpod/containers/$cid 204
+
+# Create 3 stopped containers to test containers prune
+podman run $IMAGE true
+podman run $IMAGE true
+podman run $IMAGE true
+
+t POST libpod/containers/prune '' 200
+t GET libpod/containers/json 200 \
+ length=0
# vim: filetype=sh
diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at
index ad34511c7..0ce56ee3c 100644
--- a/test/apiv2/35-networks.at
+++ b/test/apiv2/35-networks.at
@@ -9,8 +9,8 @@ t GET networks/non-existing-network 404 \
t POST libpod/networks/create?name=network1 '' 200 \
.Filename~.*/network1\\.conflist
-# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}}'
-t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}' 200 \
+# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}}'
+t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}' 200 \
.Filename~.*/network2\\.conflist
# test for empty mask
@@ -22,7 +22,8 @@ t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[0,255,255,0]}'
# network list
t GET libpod/networks/json 200
-t GET libpod/networks/json?filter=name=network1 200 \
+# filters={"name":["network1"]}
+t GET libpod/networks/json?filters=%7B%22name%22%3A%5B%22network1%22%5D%7D 200 \
length=1 \
.[0].Name=network1
t GET networks 200
@@ -34,12 +35,22 @@ length=2
#filters={"name":["network"]}
t GET networks?filters=%7B%22name%22%3A%5B%22network%22%5D%7D 200 \
length=2
-# invalid filter filters={"label":"abc"}
-t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 500 \
-.cause="only the name filter for listing networks is implemented"
-# invalid filter filters={"label":"abc","name":["network"]}
-t GET networks?filters=%7B%22label%22%3A%22abc%22%2C%22name%22%3A%5B%22network%22%5D%7D 500 \
-.cause="only the name filter for listing networks is implemented"
+# filters={"label":["abc"]}
+t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 200 \
+length=1
+# id filter filters={"id":["a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1"]}
+t GET networks?filters=%7B%22id%22%3A%5B%22a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1%22%5D%7D 200 \
+length=1 \
+.[0].Name=network1 \
+.[0].Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1
+# invalid filter filters={"dangling":["1"]}
+t GET networks?filters=%7B%22dangling%22%3A%5B%221%22%5D%7D 500 \
+.cause='invalid filter "dangling"'
+
+# network inspect docker
+t GET networks/a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 200 \
+.Name=network1 \
+.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1
# clean the network
t DELETE libpod/networks/network1 200 \
diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py
index 52348d4f4..2f9e62149 100644
--- a/test/apiv2/rest_api/test_rest_v2_0_0.py
+++ b/test/apiv2/rest_api/test_rest_v2_0_0.py
@@ -1,11 +1,13 @@
import json
+import random
+import string
import subprocess
-import sys
-import time
import unittest
from multiprocessing import Process
import requests
+import sys
+import time
from dateutil.parser import parse
from test.apiv2.rest_api import Podman
@@ -91,14 +93,21 @@ class TestApi(unittest.TestCase):
self.assertIsNotNone(r.content)
_ = json.loads(r.text)
+ info = requests.get(PODMAN_URL + "/v1.40/info")
+ self.assertEqual(info.status_code, 200, info.content)
+ _ = json.loads(info.text)
+
def test_events(self):
r = requests.get(_url("/events?stream=false"))
self.assertEqual(r.status_code, 200, r.text)
self.assertIsNotNone(r.content)
- for line in r.text.splitlines():
+
+ report = r.text.splitlines()
+ self.assertGreater(len(report), 0, "No events found!")
+ for line in report:
obj = json.loads(line)
# Actor.ID is uppercase for compatibility
- _ = obj["Actor"]["ID"]
+ self.assertIn("ID", obj["Actor"])
def test_containers(self):
r = requests.get(_url("/containers/json"), timeout=5)
@@ -172,22 +181,26 @@ class TestApi(unittest.TestCase):
self.assertEqual(net_default.status_code, 201, net_default.text)
create = requests.post(
- PODMAN_URL + "/v1.40/containers/create?name=postCreate",
+ PODMAN_URL + "/v1.40/containers/create?name=postCreateConnect",
json={
"Cmd": ["top"],
"Image": "alpine:latest",
"NetworkDisabled": False,
# FIXME adding these 2 lines cause: (This is sampled from docker-py)
# "network already exists","message":"container
- # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI network \"TestDefaultNetwork\": network already exists"
+ # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI
+ # network \"TestDefaultNetwork\": network already exists"
# "HostConfig": {"NetworkMode": "TestDefaultNetwork"},
# "NetworkingConfig": {"EndpointsConfig": {"TestDefaultNetwork": None}},
# FIXME These two lines cause:
- # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not found"
+ # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container
+ # 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not
+ # found"
# "HostConfig": {"NetworkMode": "TestNetwork"},
# "NetworkingConfig": {"EndpointsConfig": {"TestNetwork": None}},
# FIXME no networking defined cause: (note this error is from the container inspect below)
- # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error"
+ # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [
+ # TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error"
},
)
self.assertEqual(create.status_code, 201, create.text)
@@ -255,23 +268,68 @@ class TestApi(unittest.TestCase):
def test_commit(self):
r = requests.post(_url(ctnr("/commit?container={}")))
self.assertEqual(r.status_code, 200, r.text)
- validateObjectFields(r.text)
- def test_images(self):
- r = requests.get(_url("/images/json"))
+ obj = json.loads(r.content)
+ self.assertIsInstance(obj, dict)
+ self.assertIn("Id", obj)
+
+ def test_images_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/images/json")
self.assertEqual(r.status_code, 200, r.text)
- validateObjectFields(r.content)
- def test_inspect_image(self):
- r = requests.get(_url("/images/alpine/json"))
+ # See https://docs.docker.com/engine/api/v1.40/#operation/ImageList
+ required_keys = (
+ "Id",
+ "ParentId",
+ "RepoTags",
+ "RepoDigests",
+ "Created",
+ "Size",
+ "SharedSize",
+ "VirtualSize",
+ "Labels",
+ "Containers",
+ )
+ objs = json.loads(r.content)
+ self.assertIn(type(objs), (list,))
+ for o in objs:
+ self.assertIsInstance(o, dict)
+ for k in required_keys:
+ self.assertIn(k, o)
+
+ def test_inspect_image_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/images/alpine/json")
self.assertEqual(r.status_code, 200, r.text)
- obj = validateObjectFields(r.content)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/ImageInspect
+ required_keys = (
+ "Id",
+ "Parent",
+ "Comment",
+ "Created",
+ "Container",
+ "DockerVersion",
+ "Author",
+ "Architecture",
+ "Os",
+ "Size",
+ "VirtualSize",
+ "GraphDriver",
+ "RootFS",
+ "Metadata",
+ )
+
+ obj = json.loads(r.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
_ = parse(obj["Created"])
- def test_delete_image(self):
- r = requests.delete(_url("/images/alpine?force=true"))
+ def test_delete_image_compat(self):
+ r = requests.delete(PODMAN_URL + "/v1.40/images/alpine?force=true")
self.assertEqual(r.status_code, 200, r.text)
- json.loads(r.text)
+ obj = json.loads(r.content)
+ self.assertIn(type(obj), (list,))
def test_pull(self):
r = requests.post(_url("/images/pull?reference=alpine"), timeout=15)
@@ -295,12 +353,13 @@ class TestApi(unittest.TestCase):
self.assertTrue(keys["images"], "Expected to find images stanza")
self.assertTrue(keys["stream"], "Expected to find stream progress stanza's")
- def test_search(self):
+ def test_search_compat(self):
# Had issues with this test hanging when repositories not happy
def do_search():
- r = requests.get(_url("/images/search?term=alpine"), timeout=5)
+ r = requests.get(PODMAN_URL + "/v1.40/images/search?term=alpine", timeout=5)
self.assertEqual(r.status_code, 200, r.text)
- json.loads(r.text)
+ objs = json.loads(r.text)
+ self.assertIn(type(objs), (list,))
search = Process(target=do_search)
search.start()
@@ -308,17 +367,168 @@ class TestApi(unittest.TestCase):
self.assertFalse(search.is_alive(), "/images/search took too long")
def test_ping(self):
+ required_headers = (
+ "API-Version",
+ "Builder-Version",
+ "Docker-Experimental",
+ "Cache-Control",
+ "Pragma",
+ "Pragma",
+ )
+
+ def check_headers(req):
+ for k in required_headers:
+ self.assertIn(k, req.headers)
+
r = requests.get(PODMAN_URL + "/_ping")
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "OK")
+ check_headers(r)
r = requests.head(PODMAN_URL + "/_ping")
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "")
+ check_headers(r)
r = requests.get(_url("/_ping"))
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "OK")
+ check_headers(r)
- r = requests.get(_url("/_ping"))
+ r = requests.head(_url("/_ping"))
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "")
+ check_headers(r)
+
+ def test_history_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/images/alpine/history")
+ self.assertEqual(r.status_code, 200, r.text)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/ImageHistory
+ required_keys = ("Id", "Created", "CreatedBy", "Tags", "Size", "Comment")
+
+ objs = json.loads(r.content)
+ self.assertIn(type(objs), (list,))
+ for o in objs:
+ self.assertIsInstance(o, dict)
+ for k in required_keys:
+ self.assertIn(k, o)
+
+ def test_network_compat(self):
+ name = "Network_" + "".join(random.choice(string.ascii_letters) for i in range(10))
+
+ # Cannot test for 0 existing networks because default "podman" network always exists
+
+ create = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": name})
+ self.assertEqual(create.status_code, 201, create.content)
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+ self.assertIn("Id", obj)
+ ident = obj["Id"]
+ self.assertNotEqual(name, ident)
+
+ ls = requests.get(PODMAN_URL + "/v1.40/networks")
+ self.assertEqual(ls.status_code, 200, ls.content)
+ objs = json.loads(ls.content)
+ self.assertIn(type(objs), (list,))
+
+ found = False
+ for network in objs:
+ if network["Name"] == name:
+ found = True
+ self.assertTrue(found, f"Network {name} not found")
+
+ inspect = requests.get(PODMAN_URL + f"/v1.40/networks/{ident}")
+ self.assertEqual(inspect.status_code, 200, inspect.content)
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+
+ inspect = requests.delete(PODMAN_URL + f"/v1.40/networks/{ident}")
+ self.assertEqual(inspect.status_code, 204, inspect.content)
+ inspect = requests.get(PODMAN_URL + f"/v1.40/networks/{ident}")
+ self.assertEqual(inspect.status_code, 404, inspect.content)
+
+ prune = requests.post(PODMAN_URL + "/v1.40/networks/prune")
+ self.assertEqual(prune.status_code, 405, prune.content)
+
+ def test_volumes_compat(self):
+ name = "Volume_" + "".join(random.choice(string.ascii_letters) for i in range(10))
+
+ ls = requests.get(PODMAN_URL + "/v1.40/volumes")
+ self.assertEqual(ls.status_code, 200, ls.content)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeList
+ required_keys = (
+ "Volumes",
+ "Warnings",
+ )
+
+ obj = json.loads(ls.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
+
+ create = requests.post(PODMAN_URL + "/v1.40/volumes/create", json={"Name": name})
+ self.assertEqual(create.status_code, 201, create.content)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate
+ # and https://docs.docker.com/engine/api/v1.40/#operation/VolumeInspect
+ required_keys = (
+ "Name",
+ "Driver",
+ "Mountpoint",
+ "Labels",
+ "Scope",
+ "Options",
+ )
+
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
+ self.assertEqual(obj["Name"], name)
+
+ inspect = requests.get(PODMAN_URL + f"/v1.40/volumes/{name}")
+ self.assertEqual(inspect.status_code, 200, inspect.content)
+
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
+
+ rm = requests.delete(PODMAN_URL + f"/v1.40/volumes/{name}")
+ self.assertEqual(rm.status_code, 204, rm.content)
+
+ prune = requests.post(PODMAN_URL + "/v1.40/volumes/prune")
+ self.assertEqual(prune.status_code, 200, prune.content)
+
+ def test_auth_compat(self):
+ r = requests.post(
+ PODMAN_URL + "/v1.40/auth",
+ json={
+ "username": "bozo",
+ "password": "wedontneednopasswords",
+ "serveraddress": "https://localhost/v1.40/",
+ },
+ )
+ self.assertEqual(r.status_code, 404, r.content)
+
+ def test_version(self):
+ r = requests.get(PODMAN_URL + "/v1.40/version")
+ self.assertEqual(r.status_code, 200, r.content)
+
+ r = requests.get(_url("/version"))
+ self.assertEqual(r.status_code, 200, r.content)
+
+ def test_df_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/system/df")
+ self.assertEqual(r.status_code, 200, r.content)
+
+ obj = json.loads(r.content)
+ self.assertIn("Images", obj)
+ self.assertIn("Containers", obj)
+ self.assertIn("Volumes", obj)
+ self.assertIn("BuildCache", obj)
if __name__ == "__main__":
diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go
index 63a2df67a..ac9481797 100644
--- a/test/e2e/build_test.go
+++ b/test/e2e/build_test.go
@@ -239,7 +239,7 @@ RUN printenv http_proxy`
Expect(session.ExitCode()).To(Equal(0))
// Verify that OS and Arch are being set
- inspect := podmanTest.PodmanNoCache([]string{"image", "inspect", "--format", "{{ index .Config.Labels }}", "test"})
+ inspect := podmanTest.Podman([]string{"image", "inspect", "--format", "{{ index .Config.Labels }}", "test"})
inspect.WaitWithDefaultTimeout()
data := inspect.OutputToString()
Expect(data).To(ContainSubstring(buildah.Version))
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index 16d8bb770..d7bbdc633 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -317,7 +317,7 @@ func (p *PodmanTestIntegration) createArtifact(image string) {
fmt.Printf("Caching %s at %s...", image, destName)
if _, err := os.Stat(destName); os.IsNotExist(err) {
pull := p.PodmanNoCache([]string{"pull", image})
- pull.Wait(90)
+ pull.Wait(240)
Expect(pull.ExitCode()).To(Equal(0))
save := p.PodmanNoCache([]string{"save", "-o", destName, image})
diff --git a/test/e2e/config/containers.conf b/test/e2e/config/containers.conf
index 5f852468d..35153ba05 100644
--- a/test/e2e/config/containers.conf
+++ b/test/e2e/config/containers.conf
@@ -52,3 +52,7 @@ dns_options=[ "debug", ]
tz = "Pacific/Honolulu"
umask = "0002"
+
+[engine]
+
+network_cmd_options=["allow_host_loopback=true"]
diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go
index 906153c0f..28672cfc6 100644
--- a/test/e2e/containers_conf_test.go
+++ b/test/e2e/containers_conf_test.go
@@ -203,35 +203,35 @@ var _ = Describe("Podman run", func() {
session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("search foobar.com")
+ session.LineInOutputStartsWith("search foobar.com")
})
It("podman run add dns server", func() {
session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("server 1.2.3.4")
+ session.LineInOutputStartsWith("server 1.2.3.4")
})
It("podman run add dns option", func() {
session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("options debug")
+ session.LineInOutputStartsWith("options debug")
})
It("podman run containers.conf remove all search domain", func() {
session := podmanTest.Podman([]string{"run", "--dns-search=.", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("search")).To(BeFalse())
+ Expect(session.LineInOutputStartsWith("search")).To(BeFalse())
})
It("podman run use containers.conf search domain", func() {
session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("search")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("search")).To(BeTrue())
Expect(session.OutputToString()).To(ContainSubstring("foobar.com"))
Expect(session.OutputToString()).To(ContainSubstring("1.2.3.4"))
@@ -258,6 +258,12 @@ var _ = Describe("Podman run", func() {
Expect(session.OutputToString()).To(Equal("0002"))
})
+ It("podman set network cmd options slirp options to allow host loopback", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "slirp4netns", ALPINE, "ping", "-c1", "10.0.2.2"})
+ session.Wait(30)
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman-remote test localcontainers.conf versus remote containers.conf", func() {
if !IsRemote() {
Skip("this test is only for remote")
@@ -275,7 +281,7 @@ var _ = Describe("Podman run", func() {
session = podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("search")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("search")).To(BeTrue())
Expect(session.OutputToString()).To(ContainSubstring("foobar.com"))
Expect(session.OutputToString()).To(ContainSubstring("1.2.3.4"))
Expect(session.OutputToString()).To(ContainSubstring("debug"))
@@ -311,4 +317,5 @@ var _ = Describe("Podman run", func() {
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Equal("0022"))
})
+
})
diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go
index c1d3be5ab..33908b60e 100644
--- a/test/e2e/cp_test.go
+++ b/test/e2e/cp_test.go
@@ -4,14 +4,18 @@ import (
"io/ioutil"
"os"
"os/exec"
+ "os/user"
"path/filepath"
- "strings"
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
+// NOTE: Only smoke tests. The system tests (i.e., "./test/system/*") take
+// care of function and regression tests. Please consider adding system tests
+// rather than e2e tests. System tests are used in RHEL gating.
+
var _ = Describe("Podman cp", func() {
var (
tempdir string
@@ -37,240 +41,108 @@ var _ = Describe("Podman cp", func() {
})
+ // Copy a file to the container, then back to the host and make sure
+ // that the contents match.
It("podman cp file", func() {
- srcPath := filepath.Join(podmanTest.RunRoot, "cp_test.txt")
- dstPath := filepath.Join(podmanTest.RunRoot, "cp_from_container")
- fromHostToContainer := []byte("copy from host to container")
-
- session := podmanTest.Podman([]string{"create", ALPINE, "cat", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- name := session.OutputToString()
-
- err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644)
+ srcFile, err := ioutil.TempFile("", "")
Expect(err).To(BeNil())
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
- session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo/"})
- session.WaitWithDefaultTimeout()
- Expect(session).To(ExitWithError())
-
- session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", name + ":foo", dstPath})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"start", name})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- })
-
- It("podman cp file to dir", func() {
- name := "testctr"
- setup := podmanTest.RunTopContainer(name)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
-
- srcPath := "/tmp/cp_test.txt"
- fromHostToContainer := []byte("copy from host to container directory")
- err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644)
+ originalContent := []byte("podman cp file test")
+ err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644)
Expect(err).To(BeNil())
- session := podmanTest.Podman([]string{"exec", name, "mkdir", "foodir"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", srcPath, name + ":foodir/"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"exec", name, "ls", "foodir/cp_test.txt"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- os.Remove("/tmp/cp_test.txt")
- })
-
- It("podman cp dir to dir", func() {
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir1")
-
- session := podmanTest.Podman([]string{"create", ALPINE, "ls", "/foodir"})
+ // Create a container. NOTE that container mustn't be running for copying.
+ session := podmanTest.Podman([]string{"create", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
name := session.OutputToString()
- err := os.Mkdir(testDirPath, 0755)
- Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
-
- session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- testctr := "testctr"
- setup := podmanTest.RunTopContainer(testctr)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
+ // Copy TO the container.
- session = podmanTest.Podman([]string{"exec", testctr, "mkdir", "foo"})
+ // Cannot copy to a non-existent path (note the trailing "/").
+ session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo/"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", testDirPath + "/.", testctr + ":/foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"exec", testctr, "ls", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(len(session.OutputToString())).To(Equal(0))
+ Expect(session).To(ExitWithError())
- session = podmanTest.Podman([]string{"cp", testctr + ":/foo/.", testDirPath})
+ // The file will now be created (and written to).
+ session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- cmd := exec.Command("ls", testDirPath)
- res, err := cmd.Output()
- Expect(err).To(BeNil())
- Expect(len(res)).To(Equal(0))
- })
- It("podman cp stdin/stdout", func() {
- SkipIfRemote("FIXME: podman-remote cp not implemented yet")
- session := podmanTest.Podman([]string{"create", ALPINE, "ls", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- name := session.OutputToString()
+ // Copy FROM the container.
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir2")
- err := os.Mkdir(testDirPath, 0755)
- Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- cmd := exec.Command("tar", "-zcvf", "file.tar.gz", testDirPath)
- _, err = cmd.Output()
+ destFile, err := ioutil.TempFile("", "")
Expect(err).To(BeNil())
+ defer destFile.Close()
+ defer os.Remove(destFile.Name())
- data, err := ioutil.ReadFile("foo.tar.gz")
- reader := strings.NewReader(string(data))
- cmd.Stdin = reader
- session = podmanTest.Podman([]string{"cp", "-", name + ":/foo"})
+ session = podmanTest.Podman([]string{"cp", name + ":foo", destFile.Name()})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "file.tar.gz", name + ":/foo.tar.gz"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", name + ":/foo.tar.gz", "-"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- os.Remove("file.tar.gz")
- })
-
- It("podman cp tar", func() {
- testctr := "testctr"
- setup := podmanTest.RunTopContainer(testctr)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
-
- session := podmanTest.Podman([]string{"exec", testctr, "mkdir", "foo"})
+ session = podmanTest.Podman([]string{"start", name})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- path, err := os.Getwd()
- Expect(err).To(BeNil())
- testDirPath := filepath.Join(path, "TestDir3")
- err = os.Mkdir(testDirPath, 0777)
+ // Now make sure the content matches.
+ roundtripContent, err := ioutil.ReadFile(destFile.Name())
Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- cmd := exec.Command("tar", "-cvf", "file.tar", testDirPath)
- _, err = cmd.Output()
- Expect(err).To(BeNil())
-
- session = podmanTest.Podman([]string{"cp", "file.tar", "testctr:/foo/"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"exec", testctr, "ls", "-l", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("file.tar"))
-
- os.Remove("file.tar")
+ Expect(roundtripContent).To(Equal(originalContent))
})
- It("podman cp tar --extract", func() {
- testctr := "testctr"
- setup := podmanTest.RunTopContainer(testctr)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
-
- session := podmanTest.Podman([]string{"exec", testctr, "mkdir", "/foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- path, err := os.Getwd()
- Expect(err).To(BeNil())
- testDirPath := filepath.Join(path, "TestDir4")
- err = os.Mkdir(testDirPath, 0777)
- Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- f, err := os.Create(filepath.Join(testDirPath, "a.txt"))
- Expect(err).To(BeNil())
- _, err = f.Write([]byte("Hello World!!!\n"))
- f.Close()
- cmd := exec.Command("tar", "-cvf", "file.tar", "TestDir4")
- exec.Command("tar", "-cvf", "/home/mvasek/file.tar", testDirPath)
- _, err = cmd.Output()
+ // Create a symlink in the container, use it as a copy destination and
+ // make sure that the link and the resolved path are accessible and
+ // give the right content.
+ It("podman cp symlink", func() {
+ srcFile, err := ioutil.TempFile("", "")
Expect(err).To(BeNil())
- defer os.Remove("file.tar")
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
- session = podmanTest.Podman([]string{"cp", "--extract", "file.tar", "testctr:/foo/"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"exec", testctr, "cat", "/foo/TestDir4/a.txt"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("Hello World!!!"))
- })
+ originalContent := []byte("podman cp symlink test")
+ err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644)
+ Expect(err).To(BeNil())
- It("podman cp symlink", func() {
session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
name := session.OutputToString()
- srcPath := filepath.Join(podmanTest.RunRoot, "cp_test.txt")
- fromHostToContainer := []byte("copy from host to container")
- err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644)
- Expect(err).To(BeNil())
-
session = podmanTest.Podman([]string{"exec", name, "ln", "-s", "/tmp", "/test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "--pause=false", srcPath, name + ":/test"})
+ session = podmanTest.Podman([]string{"cp", "--pause=false", srcFile.Name(), name + ":/test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- _, err = os.Stat("/tmp/cp_test.txt")
- Expect(err).To(Not(BeNil()))
-
- session = podmanTest.Podman([]string{"exec", name, "ln", "-s", "/tmp/nonesuch", "/test1"})
+ session = podmanTest.Podman([]string{"exec", name, "cat", "/tmp/" + filepath.Base(srcFile.Name())})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(string(originalContent)))
- session = podmanTest.Podman([]string{"cp", "--pause=false", srcPath, name + ":/test1/"})
+ session = podmanTest.Podman([]string{"exec", name, "cat", "/test/" + filepath.Base(srcFile.Name())})
session.WaitWithDefaultTimeout()
- Expect(session).To(ExitWithError())
-
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(string(originalContent)))
})
+
+ // Copy a file to a volume in the container. The tricky part is that
+ // containers mustn't be running for copying, so Podman has to do some
+ // intense Yoga and 1) detect volume paths on the container, 2) resolve
+ // the path to the volume's mount point on the host, and 3) copy the
+ // data to the volume and not the container.
It("podman cp volume", func() {
+ srcFile, err := ioutil.TempFile("", "")
+ Expect(err).To(BeNil())
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
+
+ originalContent := []byte("podman cp volume")
+ err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644)
+ Expect(err).To(BeNil())
session := podmanTest.Podman([]string{"volume", "create", "data"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -279,23 +151,31 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- err = ioutil.WriteFile("cp_vol", []byte("copy to the volume"), 0644)
- if err != nil {
- os.Exit(1)
- }
- session = podmanTest.Podman([]string{"cp", "cp_vol", "container1" + ":/data/cp_vol1"})
+ session = podmanTest.Podman([]string{"cp", srcFile.Name(), "container1" + ":/data/file.txt"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "container1" + ":/data/cp_vol1", "cp_vol2"})
+ // Now get the volume's mount point, read the file and make
+ // sure the contents match.
+ session = podmanTest.Podman([]string{"volume", "inspect", "data", "--format", "{{.Mountpoint}}"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- os.Remove("cp_vol")
- os.Remove("cp_vol2")
+ volumeMountPoint := session.OutputToString()
+ copiedContent, err := ioutil.ReadFile(filepath.Join(volumeMountPoint, "file.txt"))
+ Expect(err).To(BeNil())
+ Expect(copiedContent).To(Equal(originalContent))
})
+ // Create another user in the container, let them create a file, copy
+ // it to the host and back to the container and make sure that we can
+ // access it, and (roughly) the right users own it.
It("podman cp from ctr chown ", func() {
+ srcFile, err := ioutil.TempFile("", "")
+ Expect(err).To(BeNil())
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
+
setup := podmanTest.RunTopContainer("testctr")
setup.WaitWithDefaultTimeout()
Expect(setup.ExitCode()).To(Equal(0))
@@ -308,17 +188,19 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "--pause=false", "testctr:/tmp/testfile", "testfile1"})
+ session = podmanTest.Podman([]string{"cp", "--pause=false", "testctr:/tmp/testfile", srcFile.Name()})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// owner of the file copied to local machine is not testuser
- cmd := exec.Command("ls", "-l", "testfile1")
+ u, err := user.Current()
+ Expect(err).To(BeNil())
+ cmd := exec.Command("ls", "-l", srcFile.Name())
cmdRet, err := cmd.Output()
Expect(err).To(BeNil())
- Expect(strings.Contains(string(cmdRet), "testuser")).To(BeFalse())
+ Expect(string(cmdRet)).To(ContainSubstring(u.Username))
- session = podmanTest.Podman([]string{"cp", "--pause=false", "testfile1", "testctr:testfile2"})
+ session = podmanTest.Podman([]string{"cp", "--pause=false", srcFile.Name(), "testctr:testfile2"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -327,45 +209,35 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring("root"))
-
- os.Remove("testfile1")
})
- It("podman cp the root directory from the ctr to an existing directory on the host ", func() {
- imgName := "test-cp-root-dir:latest"
- DockerfileName := "Dockerfile.test-cp-root-dir"
- ctrName := "test-container-cp-root"
- session := podmanTest.Podman([]string{"build", "-f", "build/" + DockerfileName, "-t", imgName, "build/"})
+ // Copy the root dir "/" of a container to the host.
+ It("podman cp the root directory from the ctr to an existing directory on the host ", func() {
+ container := "copyroottohost"
+ session := podmanTest.RunTopContainer(container)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDirForCp")
-
- session = podmanTest.Podman([]string{"create", "--name", ctrName, imgName, "dummy"})
+ session = podmanTest.Podman([]string{"exec", container, "touch", "/dummy.txt"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- err := os.Mkdir(testDirPath, 0755)
+ tmpDir, err := ioutil.TempDir("", "")
Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- // Copy the root directory of the container to an existing directory
- session = podmanTest.Podman([]string{"cp", ctrName + ":/", testDirPath})
+ session = podmanTest.Podman([]string{"cp", container + ":/", tmpDir})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- // The file should be in the directory,
- // not one layer too much of the directory called merged
- checkFile := filepath.Join(testDirPath, DockerfileName)
- _, err = os.Stat(checkFile)
+ cmd := exec.Command("ls", "-la", tmpDir)
+ output, err := cmd.Output()
+ lsOutput := string(output)
Expect(err).To(BeNil())
-
- session = podmanTest.Podman([]string{"container", "rm", ctrName})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"rmi", "-f", imgName})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(lsOutput).To(ContainSubstring("dummy.txt"))
+ Expect(lsOutput).To(ContainSubstring("tmp"))
+ Expect(lsOutput).To(ContainSubstring("etc"))
+ Expect(lsOutput).To(ContainSubstring("var"))
+ Expect(lsOutput).To(ContainSubstring("bin"))
+ Expect(lsOutput).To(ContainSubstring("usr"))
})
})
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index 760345a67..aaf089d97 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "runtime"
"strings"
. "github.com/containers/podman/v2/test/utils"
@@ -644,4 +645,35 @@ var _ = Describe("Podman create", func() {
Expect(session.ErrorToString()).To(ContainSubstring("unknown flag"))
})
+ It("podman create --platform", func() {
+ session := podmanTest.Podman([]string{"create", "--platform=linux/bogus", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError := "no image found in manifest list for architecture bogus"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"create", "--platform=linux/arm64", "--override-os", "windows", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError = "--platform option can not be specified with --overide-arch or --override-os"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"create", "-q", "--platform=linux/arm64", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ setup := podmanTest.Podman([]string{"container", "inspect", session.OutputToString()})
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ data := setup.InspectContainerToJSON()
+ setup = podmanTest.Podman([]string{"image", "inspect", data[0].Image})
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ idata := setup.InspectImageJSON() // returns []inspect.ImageData
+ Expect(len(idata)).To(Equal(1))
+ Expect(idata[0].Os).To(Equal(runtime.GOOS))
+ Expect(idata[0].Architecture).To(Equal("arm64"))
+ })
})
diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go
index 4c65a85d5..281b2c313 100644
--- a/test/e2e/images_test.go
+++ b/test/e2e/images_test.go
@@ -41,8 +41,8 @@ var _ = Describe("Podman images", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
- Expect(session.LineInOuputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
- Expect(session.LineInOuputStartsWith("quay.io/libpod/busybox")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("quay.io/libpod/busybox")).To(BeTrue())
})
It("podman image List", func() {
@@ -50,8 +50,8 @@ var _ = Describe("Podman images", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
- Expect(session.LineInOuputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
- Expect(session.LineInOuputStartsWith("quay.io/libpod/busybox")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("quay.io/libpod/busybox")).To(BeTrue())
})
It("podman images with multiple tags", func() {
@@ -86,8 +86,8 @@ var _ = Describe("Podman images", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
- Expect(session.LineInOuputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
- Expect(session.LineInOuputStartsWith("quay.io/libpod/busybox")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("quay.io/libpod/busybox")).To(BeTrue())
})
It("podman empty images list in JSON format", func() {
@@ -278,7 +278,7 @@ WORKDIR /test
It("podman images sort by values", func() {
sortValueTest := func(value string, result int, format string) []string {
f := fmt.Sprintf("{{.%s}}", format)
- session := podmanTest.Podman([]string{"images", "--sort", value, "--format", f})
+ session := podmanTest.Podman([]string{"images", "--noheading", "--sort", value, "--format", f})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(result))
diff --git a/test/e2e/import_test.go b/test/e2e/import_test.go
index 1be4ef920..bd465bf38 100644
--- a/test/e2e/import_test.go
+++ b/test/e2e/import_test.go
@@ -87,7 +87,7 @@ var _ = Describe("Podman import", func() {
results := podmanTest.Podman([]string{"history", "imported-image", "--format", "{{.Comment}}"})
results.WaitWithDefaultTimeout()
Expect(results.ExitCode()).To(Equal(0))
- Expect(results.LineInOuputStartsWith("importing container test message")).To(BeTrue())
+ Expect(results.LineInOutputStartsWith("importing container test message")).To(BeTrue())
})
It("podman import with change flag CMD=<path>", func() {
diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go
index 043046c33..21b3074fc 100644
--- a/test/e2e/network_create_test.go
+++ b/test/e2e/network_create_test.go
@@ -329,4 +329,37 @@ var _ = Describe("Podman network create", func() {
Expect(nc).To(ExitWithError())
})
+ It("podman network create with mtu option", func() {
+ net := "mtu-test"
+ nc := podmanTest.Podman([]string{"network", "create", "--opt", "mtu=9000", net})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(net)
+
+ nc = podmanTest.Podman([]string{"network", "inspect", net})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+ Expect(nc.OutputToString()).To(ContainSubstring(`"mtu": 9000,`))
+ })
+
+ It("podman network create with vlan option", func() {
+ net := "vlan-test"
+ nc := podmanTest.Podman([]string{"network", "create", "--opt", "vlan=9", net})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(net)
+
+ nc = podmanTest.Podman([]string{"network", "inspect", net})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+ Expect(nc.OutputToString()).To(ContainSubstring(`"vlan": 9`))
+ })
+
+ It("podman network create with invalid option", func() {
+ net := "invalid-test"
+ nc := podmanTest.Podman([]string{"network", "create", "--opt", "foo=bar", net})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc).To(ExitWithError())
+ })
+
})
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index 20e1d5b6b..ffc914bc2 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -66,6 +66,65 @@ var _ = Describe("Podman network", func() {
Expect(session.LineInOutputContains(name)).To(BeTrue())
})
+ It("podman network list --filter plugin and name", func() {
+ name, path := generateNetworkConfig(podmanTest)
+ defer removeConf(path)
+
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "plugin=bridge", "--filter", "name=" + name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(name))
+ })
+
+ It("podman network list --filter two names", func() {
+ name1, path1 := generateNetworkConfig(podmanTest)
+ defer removeConf(path1)
+
+ name2, path2 := generateNetworkConfig(podmanTest)
+ defer removeConf(path2)
+
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "name=" + name1, "--filter", "name=" + name2})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(name1))
+ Expect(session.OutputToString()).To(ContainSubstring(name2))
+ })
+
+ It("podman network list --filter labels", func() {
+ net1 := "labelnet" + stringid.GenerateNonCryptoID()
+ label1 := "testlabel1=abc"
+ label2 := "abcdef"
+ session := podmanTest.Podman([]string{"network", "create", "--label", label1, net1})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeCNINetwork(net1)
+ Expect(session.ExitCode()).To(BeZero())
+
+ net2 := "labelnet" + stringid.GenerateNonCryptoID()
+ session = podmanTest.Podman([]string{"network", "create", "--label", label1, "--label", label2, net2})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeCNINetwork(net2)
+ Expect(session.ExitCode()).To(BeZero())
+
+ session = podmanTest.Podman([]string{"network", "ls", "--filter", "label=" + label1})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(net1))
+ Expect(session.OutputToString()).To(ContainSubstring(net2))
+
+ session = podmanTest.Podman([]string{"network", "ls", "--filter", "label=" + label1, "--filter", "label=" + label2})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).ToNot(ContainSubstring(net1))
+ Expect(session.OutputToString()).To(ContainSubstring(net2))
+ })
+
+ It("podman network list --filter invalid value", func() {
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "namr=ab"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(ExitWithError())
+ Expect(session.ErrorToString()).To(ContainSubstring(`invalid filter "namr"`))
+ })
+
It("podman network list --filter failure", func() {
name, path := generateNetworkConfig(podmanTest)
defer removeConf(path)
@@ -76,6 +135,40 @@ var _ = Describe("Podman network", func() {
Expect(session.LineInOutputContains(name)).To(BeFalse())
})
+ It("podman network ID test", func() {
+ net := "networkIDTest"
+ // the network id should be the sha256 hash of the network name
+ netID := "6073aefe03cdf8f29be5b23ea9795c431868a3a22066a6290b187691614fee84"
+ session := podmanTest.Podman([]string{"network", "create", net})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeCNINetwork(net)
+ Expect(session.ExitCode()).To(BeZero())
+
+ session = podmanTest.Podman([]string{"network", "ls", "--format", "{{.Name}} {{.ID}}", "--filter", "id=" + netID})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ Expect(session.OutputToString()).To(ContainSubstring(net + " " + netID[:12]))
+
+ session = podmanTest.Podman([]string{"network", "ls", "--format", "{{.Name}} {{.ID}}", "--filter", "id=" + netID[10:50], "--no-trunc"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ Expect(session.OutputToString()).To(ContainSubstring(net + " " + netID))
+
+ session = podmanTest.Podman([]string{"network", "inspect", netID[:40]})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ Expect(session.OutputToString()).To(ContainSubstring(net))
+
+ session = podmanTest.Podman([]string{"network", "inspect", netID[1:]})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).ToNot(BeZero())
+ Expect(session.ErrorToString()).To(ContainSubstring("no such network"))
+
+ session = podmanTest.Podman([]string{"network", "rm", netID})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ })
+
rm_func := func(rm string) {
It(fmt.Sprintf("podman network %s no args", rm), func() {
session := podmanTest.Podman([]string{"network", rm})
diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go
index a3ce8bd69..4aaf2cbc1 100644
--- a/test/e2e/port_test.go
+++ b/test/e2e/port_test.go
@@ -61,7 +61,7 @@ var _ = Describe("Podman port", func() {
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
port := strings.Split(result.OutputToStringArray()[0], ":")[1]
- Expect(result.LineInOuputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue())
+ Expect(result.LineInOutputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue())
})
It("podman container port -l nginx", func() {
@@ -79,7 +79,7 @@ var _ = Describe("Podman port", func() {
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
port := strings.Split(result.OutputToStringArray()[0], ":")[1]
- Expect(result.LineInOuputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue())
+ Expect(result.LineInOutputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue())
})
It("podman port -l port nginx", func() {
@@ -97,7 +97,7 @@ var _ = Describe("Podman port", func() {
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
port := strings.Split(result.OutputToStringArray()[0], ":")[1]
- Expect(result.LineInOuputStartsWith(fmt.Sprintf("0.0.0.0:%s", port))).To(BeTrue())
+ Expect(result.LineInOutputStartsWith(fmt.Sprintf("0.0.0.0:%s", port))).To(BeTrue())
})
It("podman port -a nginx", func() {
@@ -124,7 +124,7 @@ var _ = Describe("Podman port", func() {
result := podmanTest.Podman([]string{"port", "portcheck"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
- result.LineInOuputStartsWith("80/tcp -> 0.0.0.0:")
+ result.LineInOutputStartsWith("80/tcp -> 0.0.0.0:")
})
It("podman port multiple ports", func() {
@@ -142,12 +142,12 @@ var _ = Describe("Podman port", func() {
result1 := podmanTest.Podman([]string{"port", "test", "5000"})
result1.WaitWithDefaultTimeout()
Expect(result1.ExitCode()).To(BeZero())
- Expect(result1.LineInOuputStartsWith("0.0.0.0:5000")).To(BeTrue())
+ Expect(result1.LineInOutputStartsWith("0.0.0.0:5000")).To(BeTrue())
// Check that the second port was honored
result2 := podmanTest.Podman([]string{"port", "test", "5001"})
result2.WaitWithDefaultTimeout()
Expect(result2.ExitCode()).To(BeZero())
- Expect(result2.LineInOuputStartsWith("0.0.0.0:5001")).To(BeTrue())
+ Expect(result2.LineInOutputStartsWith("0.0.0.0:5001")).To(BeTrue())
})
})
diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go
index f1b055d6d..446e2bd38 100644
--- a/test/e2e/pull_test.go
+++ b/test/e2e/pull_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "runtime"
"strings"
. "github.com/containers/podman/v2/test/utils"
@@ -384,7 +385,7 @@ var _ = Describe("Podman pull", func() {
session := podmanTest.Podman([]string{"pull", "--all-tags", "k8s.gcr.io/pause"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("Pulled Images:")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("Pulled Images:")).To(BeTrue())
session = podmanTest.Podman([]string{"images"})
session.WaitWithDefaultTimeout()
@@ -494,4 +495,31 @@ var _ = Describe("Podman pull", func() {
Expect(data[0].ID).To(Equal(image1))
}
})
+
+ It("podman pull --platform", func() {
+ session := podmanTest.Podman([]string{"pull", "--platform=linux/bogus", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError := "no image found in manifest list for architecture bogus"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"pull", "--platform=linux/arm64", "--override-os", "windows", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ expectedError = "--platform option can not be specified with --overide-arch or --override-os"
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedError))
+
+ session = podmanTest.Podman([]string{"pull", "-q", "--platform=linux/arm64", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ setup := podmanTest.Podman([]string{"image", "inspect", session.OutputToString()})
+ setup.WaitWithDefaultTimeout()
+ Expect(setup.ExitCode()).To(Equal(0))
+
+ data := setup.InspectImageJSON() // returns []inspect.ImageData
+ Expect(len(data)).To(Equal(1))
+ Expect(data[0].Os).To(Equal(runtime.GOOS))
+ Expect(data[0].Architecture).To(Equal("arm64"))
+ })
})
diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go
index ff018f5d8..c8996c5e8 100644
--- a/test/e2e/run_dns_test.go
+++ b/test/e2e/run_dns_test.go
@@ -36,14 +36,14 @@ var _ = Describe("Podman run dns", func() {
session := podmanTest.Podman([]string{"run", "--dns-search=foobar.com", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("search foobar.com")
+ session.LineInOutputStartsWith("search foobar.com")
})
It("podman run remove all search domain", func() {
session := podmanTest.Podman([]string{"run", "--dns-search=.", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("search")).To(BeFalse())
+ Expect(session.LineInOutputStartsWith("search")).To(BeFalse())
})
It("podman run add bad dns server", func() {
@@ -56,14 +56,14 @@ var _ = Describe("Podman run dns", func() {
session := podmanTest.Podman([]string{"run", "--dns=1.2.3.4", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("server 1.2.3.4")
+ session.LineInOutputStartsWith("server 1.2.3.4")
})
It("podman run add dns option", func() {
session := podmanTest.Podman([]string{"run", "--dns-opt=debug", ALPINE, "cat", "/etc/resolv.conf"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("options debug")
+ session.LineInOutputStartsWith("options debug")
})
It("podman run add bad host", func() {
@@ -76,8 +76,8 @@ var _ = Describe("Podman run dns", func() {
session := podmanTest.Podman([]string{"run", "--add-host=foobar:1.1.1.1", "--add-host=foobaz:2001:db8::68", ALPINE, "cat", "/etc/hosts"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session.LineInOuputStartsWith("1.1.1.1 foobar")
- session.LineInOuputStartsWith("2001:db8::68 foobaz")
+ session.LineInOutputStartsWith("1.1.1.1 foobar")
+ session.LineInOutputStartsWith("2001:db8::68 foobaz")
})
It("podman run add hostname", func() {
diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go
index 2185d6b13..cac3d759d 100644
--- a/test/e2e/run_entrypoint_test.go
+++ b/test/e2e/run_entrypoint_test.go
@@ -99,12 +99,12 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("Linux")).To(BeTrue())
session = podmanTest.Podman([]string{"run", "--entrypoint", "", "foobar.com/entrypoint:latest", "uname"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("Linux")).To(BeTrue())
})
It("podman run user entrypoint with command overrides image entrypoint and image cmd", func() {
@@ -116,6 +116,6 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest", "-r"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.LineInOuputStartsWith("Linux")).To(BeFalse())
+ Expect(session.LineInOutputStartsWith("Linux")).To(BeFalse())
})
})
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 3e80e953e..3fb00a28b 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -49,9 +49,28 @@ var _ = Describe("Podman run networking", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman run network connection with default", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "default", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman run network connection with none", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "none", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ Expect(session.ErrorToString()).To(ContainSubstring("wget: bad address 'www.podman.io'"))
+ })
+
+ It("podman run network connection with private", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "private", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman run network connection with loopback", func() {
- session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.podman.io"})
- session.Wait(90)
+ session := podmanTest.Podman([]string{"run", "--network", "host", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 5831bb2f9..58ef9a647 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -233,6 +233,39 @@ var _ = Describe("Podman run", func() {
return jsonFile
}
+ It("podman run mask and unmask path test", func() {
+ session := podmanTest.Podman([]string{"run", "-d", "--name=maskCtr1", "--security-opt", "unmask=ALL", "--security-opt", "mask=/proc/acpi", ALPINE, "sleep", "200"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr1", "ls", "/sys/firmware"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(Not(BeEmpty()))
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr1", "ls", "/proc/acpi"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(BeEmpty())
+
+ session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr2", "--security-opt", "unmask=/proc/acpi:/sys/firmware", ALPINE, "sleep", "200"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr2", "ls", "/sys/firmware"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(Not(BeEmpty()))
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr2", "ls", "/proc/acpi"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(Not(BeEmpty()))
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr3", "--security-opt", "mask=/sys/power/disk", ALPINE, "sleep", "200"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr3", "cat", "/sys/power/disk"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(BeEmpty())
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman run seccomp test", func() {
session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"})
session.WaitWithDefaultTimeout()
diff --git a/test/system/010-images.bats b/test/system/010-images.bats
index ee6da30ec..76caf282b 100644
--- a/test/system/010-images.bats
+++ b/test/system/010-images.bats
@@ -199,9 +199,16 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
local format=$2
run_podman images --sort repository --format "$format"
- _check_line 0 ${aaa_name} ${aaa_tag}
- _check_line 1 "${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/${PODMAN_TEST_IMAGE_NAME}" "${PODMAN_TEST_IMAGE_TAG}"
- _check_line 2 ${zzz_name} ${zzz_tag}
+
+ line_no=0
+ if [[ $format == table* ]]; then
+ # skip headers from table command
+ line_no=1
+ fi
+
+ _check_line $line_no ${aaa_name} ${aaa_tag}
+ _check_line $((line_no+1)) "${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/${PODMAN_TEST_IMAGE_NAME}" "${PODMAN_TEST_IMAGE_TAG}"
+ _check_line $((line_no+2)) ${zzz_name} ${zzz_tag}
}
# Begin the test: tag $IMAGE with both the given names
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index 37695f205..3ee141f5f 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -536,6 +536,43 @@ json-file | f
run_podman untag $IMAGE $newtag $newtag2
}
+# Regression test for issue #8558
+@test "podman run on untagged image: make sure that image metadata is set" {
+ run_podman inspect $IMAGE --format "{{.ID}}"
+ imageID="$output"
+
+ # prior to #8623 `podman run` would error out on untagged images with:
+ # Error: both RootfsImageName and RootfsImageID must be set if either is set: invalid argument
+ run_podman untag $IMAGE
+ run_podman run --rm $imageID ls
+
+ run_podman tag $imageID $IMAGE
+}
+
+@test "Verify /run/.containerenv exist" {
+ run_podman run --rm $IMAGE ls -1 /run/.containerenv
+ is "$output" "/run/.containerenv"
+
+ run_podman run --privileged --rm $IMAGE sh -c '. /run/.containerenv; echo $engine'
+ is "$output" ".*podman.*" "failed to identify engine"
+
+ run_podman run --privileged --name "testcontainerenv" --rm $IMAGE sh -c '. /run/.containerenv; echo $name'
+ is "$output" ".*testcontainerenv.*"
+
+ run_podman run --privileged --rm $IMAGE sh -c '. /run/.containerenv; echo $image'
+ is "$output" ".*$IMAGE.*" "failed to idenitfy image"
+
+ run_podman run --privileged --rm $IMAGE sh -c '. /run/.containerenv; echo $rootless'
+ # FIXME: on some CI systems, 'run --privileged' emits a spurious
+ # warning line about dup devices. Ignore it.
+ remove_same_dev_warning
+ if is_rootless; then
+ is "$output" "1"
+ else
+ is "$output" "0"
+ fi
+}
+
@test "podman run with --net=host and --port prints warning" {
rand=$(random_string 10)
diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats
index a3d6a5800..a081a7ce1 100644
--- a/test/system/035-logs.bats
+++ b/test/system/035-logs.bats
@@ -21,6 +21,9 @@ load helpers
run_podman logs $cid
is "$output" "$rand_string" "output from podman-logs after container is run"
+ # test --since with Unix timestamps
+ run_podman logs --since 1000 $cid
+
run_podman rm $cid
}
diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats
index 6bf897790..43bdf217d 100644
--- a/test/system/065-cp.bats
+++ b/test/system/065-cp.bats
@@ -7,6 +7,290 @@
load helpers
+@test "podman cp file from host to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
+ mkdir -p $srcdir
+ local -a randomcontent=(
+ random-0-$(random_string 10)
+ random-1-$(random_string 15)
+ random-2-$(random_string 20)
+ )
+ echo "${randomcontent[0]}" > $srcdir/hostfile0
+ echo "${randomcontent[1]}" > $srcdir/hostfile1
+ echo "${randomcontent[2]}" > $srcdir/hostfile2
+
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer mkdir /srv/subdir
+
+ # format is: <id> | <destination arg to cp> | <full dest path> | <test name>
+ # where:
+ # id is 0-2, one of the random strings/files
+ # dest arg is the right-hand argument to 'podman cp' (may be implicit)
+ # dest path is the full explicit path we expect to see
+ # test name is a short description of what we're testing here
+ tests="
+0 | / | /hostfile0 | copy to root
+0 | /anotherbase.txt | /anotherbase.txt | copy to root, new name
+0 | /tmp | /tmp/hostfile0 | copy to /tmp
+1 | /tmp/ | /tmp/hostfile1 | copy to /tmp/
+2 | /tmp/. | /tmp/hostfile2 | copy to /tmp/.
+0 | /tmp/hostfile2 | /tmp/hostfile2 | overwrite previous copy
+0 | /tmp/anotherbase.txt | /tmp/anotherbase.txt | copy to /tmp, new name
+0 | . | /srv/hostfile0 | copy to workdir (rel path), new name
+1 | ./ | /srv/hostfile1 | copy to workdir (rel path), new name
+0 | anotherbase.txt | /srv/anotherbase.txt | copy to workdir (rel path), new name
+0 | subdir | /srv/subdir/hostfile0 | copy to workdir/subdir
+"
+
+ # Copy one of the files into container, exec+cat, confirm the file
+ # is there and matches what we expect
+ while read id dest dest_fullname description; do
+ run_podman cp $srcdir/hostfile$id cpcontainer:$dest
+ run_podman exec cpcontainer cat $dest_fullname
+ is "$output" "${randomcontent[$id]}" "$description (cp -> ctr:$dest)"
+ done < <(parse_table "$tests")
+
+ # Host path does not exist.
+ run_podman 125 cp $srcdir/IdoNotExist cpcontainer:/tmp
+ is "$output" 'Error: ".*/IdoNotExist" could not be found on the host' \
+ "copy nonexistent host path"
+
+ # Container path does not exist. Notice that the error message shows how
+ # the specified container is resolved.
+ run_podman 125 cp $srcdir/hostfile0 cpcontainer:/IdoNotExist/
+ is "$output" 'Error: "/IdoNotExist/" could not be found on container.*(resolved to .*/IdoNotExist.*' \
+ "copy into nonexistent path in container"
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp --extract=true tar archive to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ # Create tempfile with random name and content
+ dirname=cp-test-extract
+ srcdir=$PODMAN_TMPDIR/$dirname
+ mkdir -p $srcdir
+ rand_filename=$(random_string 20)
+ rand_content=$(random_string 50)
+ echo $rand_content > $srcdir/$rand_filename
+ chmod 644 $srcdir/$rand_filename
+
+ # Now tar it up!
+ tar_file=$PODMAN_TMPDIR/archive.tar.gz
+ tar -C $PODMAN_TMPDIR -zvcf $tar_file $dirname
+
+ run_podman run -d --name cpcontainer $IMAGE sleep infinity
+
+ # First just copy without extracting the archive.
+ run_podman cp $tar_file cpcontainer:/tmp
+ # Now remove the archive which will also test if it exists and is a file.
+ # To save expensive exec'ing, create a file for the next tests.
+ run_podman exec cpcontainer sh -c "rm /tmp/archive.tar.gz; touch /tmp/file.txt"
+
+ # Now copy with extracting the archive. NOTE that Podman should
+ # auto-decompress the file if needed.
+ run_podman cp --extract=true $tar_file cpcontainer:/tmp
+ run_podman exec cpcontainer cat /tmp/$dirname/$rand_filename
+ is "$output" "$rand_content"
+
+ # Test extract on non archive.
+ run_podman cp --extract=true $srcdir/$rand_filename cpcontainer:/foo.txt
+
+ # Cannot extract an archive to a file!
+ run_podman 125 cp --extract=true $tar_file cpcontainer:/tmp/file.txt
+ is "$output" 'Error: cannot extract archive .* to file "/tmp/file.txt"'
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp file from container to host" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-file-ctr-to-host
+ mkdir -p $srcdir
+
+ # Create 3 files with random content in the container.
+ local -a randomcontent=(
+ random-0-$(random_string 10)
+ random-1-$(random_string 15)
+ random-2-$(random_string 20)
+ )
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/containerfile"
+ run_podman exec cpcontainer sh -c "echo ${randomcontent[1]} > /srv/containerfile1"
+ run_podman exec cpcontainer sh -c "mkdir /srv/subdir; echo ${randomcontent[2]} > /srv/subdir/containerfile2"
+
+ # format is: <id> | <source arg to cp> | <destination arg (appended to $srcdir) to cp> | <full dest path (appended to $srcdir)> | <test name>
+ tests="
+0 | /tmp/containerfile | | /containerfile | copy to srcdir/
+0 | /tmp/containerfile | / | /containerfile | copy to srcdir/
+0 | /tmp/containerfile | /. | /containerfile | copy to srcdir/.
+0 | /tmp/containerfile | /newfile | /newfile | copy to srcdir/newfile
+1 | containerfile1 | / | /containerfile1 | copy from workdir (rel path) to srcdir
+2 | subdir/containerfile2 | / | /containerfile2 | copy from workdir/subdir (rel path) to srcdir
+"
+
+ # Copy one of the files to the host, cat, confirm the file
+ # is there and matches what we expect
+ while read id src dest dest_fullname description; do
+ # dest may be "''" for empty table cells
+ if [[ $dest == "''" ]];then
+ unset dest
+ fi
+ run_podman cp cpcontainer:$src "$srcdir$dest"
+ run cat $srcdir$dest_fullname
+ is "$output" "${randomcontent[$id]}" "$description (cp ctr:$src to \$srcdir$dest)"
+ rm $srcdir/$dest_fullname
+ done < <(parse_table "$tests")
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp dir from host to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ dirname=dir-test
+ srcdir=$PODMAN_TMPDIR/$dirname
+ mkdir -p $srcdir
+ local -a randomcontent=(
+ random-0-$(random_string 10)
+ random-1-$(random_string 15)
+ )
+ echo "${randomcontent[0]}" > $srcdir/hostfile0
+ echo "${randomcontent[1]}" > $srcdir/hostfile1
+
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer mkdir /srv/subdir
+
+ # format is: <source arg to cp (appended to srcdir)> | <destination arg to cp> | <full dest path> | <test name>
+ tests="
+ | / | /dir-test | copy to root
+ / | /tmp | /tmp/dir-test | copy to tmp
+ /. | /usr/ | /usr/ | copy contents of dir to usr/
+ | . | /srv/dir-test | copy to workdir (rel path)
+ | subdir/. | /srv/subdir/dir-test | copy to workdir subdir (rel path)
+"
+
+ while read src dest dest_fullname description; do
+ # src may be "''" for empty table cells
+ if [[ $src == "''" ]];then
+ unset src
+ fi
+ run_podman cp $srcdir$src cpcontainer:$dest
+ run_podman exec cpcontainer ls $dest_fullname
+ run_podman exec cpcontainer cat $dest_fullname/hostfile0
+ is "$output" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
+ run_podman exec cpcontainer cat $dest_fullname/hostfile1
+ is "$output" "${randomcontent[1]}" "$description (cp -> ctr:$dest)"
+ done < <(parse_table "$tests")
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp dir from container to host" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/dir-test
+ mkdir -p $srcdir
+
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer sh -c 'mkdir /srv/subdir; echo "This first file is on the container" > /srv/subdir/containerfile1'
+ run_podman exec cpcontainer sh -c 'echo "This second file is on the container as well" > /srv/subdir/containerfile2'
+
+ run_podman cp cpcontainer:/srv $srcdir
+ run cat $srcdir/srv/subdir/containerfile1
+ is "$output" "This first file is on the container"
+ run cat $srcdir/srv/subdir/containerfile2
+ is "$output" "This second file is on the container as well"
+ rm -rf $srcdir/srv/subdir
+
+ run_podman cp cpcontainer:/srv/. $srcdir
+ run ls $srcdir/subdir
+ run cat $srcdir/subdir/containerfile1
+ is "$output" "This first file is on the container"
+ run cat $srcdir/subdir/containerfile2
+ is "$output" "This second file is on the container as well"
+ rm -rf $srcdir/subdir
+
+ run_podman cp cpcontainer:/srv/subdir/. $srcdir
+ run cat $srcdir/containerfile1
+ is "$output" "This first file is on the container"
+ run cat $srcdir/containerfile2
+ is "$output" "This second file is on the container as well"
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp file from host to container volume" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-volume
+ mkdir -p $srcdir
+ echo "This file should be in volume2" > $srcdir/hostfile
+ volume1=$(random_string 20)
+ volume2=$(random_string 20)
+
+ run_podman volume create $volume1
+ run_podman volume inspect $volume1 --format "{{.Mountpoint}}"
+ volume1_mount="$output"
+ run_podman volume create $volume2
+ run_podman volume inspect $volume2 --format "{{.Mountpoint}}"
+ volume2_mount="$output"
+
+ # Create a container using the volume. Note that copying on not-running
+ # containers is allowed, so Podman has to analyze the container paths and
+ # check if they are hitting a volume, and eventually resolve to the path on
+ # the *host*.
+ # This test is extra tricky, as volume2 is mounted into a sub-directory of
+ # volume1. Podman must copy the file into volume2 and not volume1.
+ run_podman create --name cpcontainer -v $volume1:/tmp/volume -v $volume2:/tmp/volume/sub-volume $IMAGE
+
+ run_podman cp $srcdir/hostfile cpcontainer:/tmp/volume/sub-volume
+
+ run cat $volume2_mount/hostfile
+ is "$output" "This file should be in volume2"
+
+ # Volume 1 must be empty.
+ run ls $volume1_mount
+ is "$output" ""
+
+ run_podman rm -f cpcontainer
+ run_podman volume rm $volume1 $volume2
+}
+
+
+@test "podman cp file from host to container mount" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-mount-src
+ mountdir=$PODMAN_TMPDIR/cp-test-mount
+ mkdir -p $srcdir $mountdir
+ echo "This file should be in the mount" > $srcdir/hostfile
+
+ volume=$(random_string 20)
+ run_podman volume create $volume
+
+ # Make it a bit more complex and put the mount on a volume.
+ run_podman create --name cpcontainer -v $volume:/tmp/volume -v $mountdir:/tmp/volume/mount $IMAGE
+
+ run_podman cp $srcdir/hostfile cpcontainer:/tmp/volume/mount
+
+ run cat $mountdir/hostfile
+ is "$output" "This file should be in the mount"
+
+ run_podman rm -f cpcontainer
+ run_podman volume rm $volume
+}
+
+
# Create two random-name random-content files in /tmp in the container
# podman-cp them into the host using '/tmp/*', i.e. asking podman to
# perform wildcard expansion in the container. We should get both
@@ -51,8 +335,7 @@ load helpers
run_podman 125 cp 'cpcontainer:/tmp/*' $dstdir/
# FIXME: this might not be the exactly correct error message
- is "$output" ".*error evaluating symlinks.*lstat.*no such file or dir" \
- "Expected error from copying invalid symlink"
+ is "$output" 'Error: "/tmp/\*" could not be found on container.*'
# make sure there are no files in dstdir
is "$(/bin/ls -1 $dstdir)" "" "incorrectly copied symlink from host"
@@ -78,8 +361,7 @@ load helpers
sh -c "ln -s $srcdir/hostfile file1;ln -s file\* copyme"
run_podman 125 cp cpcontainer:copyme $dstdir
- is "$output" ".*error evaluating symlinks.*lstat.*no such file or dir" \
- "Expected error from copying invalid symlink"
+ is "$output" 'Error: "copyme*" could not be found on container.*'
# make sure there are no files in dstdir
is "$(/bin/ls -1 $dstdir)" "" "incorrectly copied symlink from host"
@@ -101,8 +383,7 @@ load helpers
sh -c "ln -s $srcdir/hostfile /tmp/\*"
run_podman 125 cp 'cpcontainer:/tmp/*' $dstdir
- is "$output" ".*error evaluating symlinks.*lstat.*no such file or dir" \
- "Expected error from copying invalid symlink"
+ is "$output" 'Error: "/tmp/\*" could not be found on container.*'
# dstdir must be empty
is "$(/bin/ls -1 $dstdir)" "" "incorrectly copied symlink from host"
@@ -110,8 +391,6 @@ load helpers
run_podman rm cpcontainer
}
-###############################################################################
-# cp INTO container
# THIS IS EXTREMELY WEIRD. Podman expands symlinks in weird ways.
@test "podman cp into container: weird symlink expansion" {
@@ -148,7 +427,7 @@ load helpers
is "$output" "" "output from podman cp 1"
run_podman 125 cp --pause=false $srcdir/$rand_filename2 cpcontainer:/tmp/d2/x/
- is "$output" ".*stat.* no such file or directory" "cp will not create nonexistent destination directory"
+ is "$output" 'Error: "/tmp/d2/x/" could not be found on container.*' "cp will not create nonexistent destination directory"
run_podman cp --pause=false $srcdir/$rand_filename3 cpcontainer:/tmp/d3/x
is "$output" "" "output from podman cp 3"
@@ -160,6 +439,7 @@ load helpers
run_podman exec cpcontainer cat /tmp/nonesuch1
is "$output" "$rand_content1" "cp creates destination file"
+
# cp into nonexistent directory should not mkdir nonesuch2 directory
run_podman 1 exec cpcontainer test -e /tmp/nonesuch2
@@ -168,8 +448,6 @@ load helpers
is "$output" "$rand_content3" "cp creates file named x"
run_podman rm -f cpcontainer
-
-
}
@@ -212,6 +490,103 @@ load helpers
}
+@test "podman cp from stdin to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ # Create tempfile with random name and content
+ srcdir=$PODMAN_TMPDIR/cp-test-stdin
+ mkdir -p $srcdir
+ rand_filename=$(random_string 20)
+ rand_content=$(random_string 50)
+ echo $rand_content > $srcdir/$rand_filename
+ chmod 644 $srcdir/$rand_filename
+
+ # Now tar it up!
+ tar_file=$PODMAN_TMPDIR/archive.tar.gz
+ tar -zvcf $tar_file $srcdir
+
+ run_podman run -d --name cpcontainer $IMAGE sleep infinity
+
+ # NOTE: podman is supposed to auto-detect the gzip compression and
+ # decompress automatically.
+ #
+ # "-" will evaluate to "/dev/stdin" when used a source.
+ run_podman cp - cpcontainer:/tmp < $tar_file
+ run_podman exec cpcontainer cat /tmp/$srcdir/$rand_filename
+ is "$output" "$rand_content"
+ run_podman exec cpcontainer rm -rf /tmp/$srcdir
+
+ # Now for "/dev/stdin".
+ run_podman cp /dev/stdin cpcontainer:/tmp < $tar_file
+ run_podman exec cpcontainer cat /tmp/$srcdir/$rand_filename
+ is "$output" "$rand_content"
+
+ # Error checks below ...
+
+ # Input stream must be a (compressed) tar archive.
+ run_podman 125 cp - cpcontainer:/tmp < $srcdir/$rand_filename
+ is "$output" "Error:.*: error reading tar stream.*" "input stream must be a (compressed) tar archive"
+
+ # Destination must be a directory (on an existing file).
+ run_podman exec cpcontainer touch /tmp/file.txt
+ run_podman 125 cp /dev/stdin cpcontainer:/tmp/file.txt < $tar_file
+ is "$output" 'Error: destination must be a directory or stream when copying from a stream'
+
+ # Destination must be a directory (on an absent path).
+ run_podman 125 cp /dev/stdin cpcontainer:/tmp/IdoNotExist < $tar_file
+ is "$output" 'Error: destination must be a directory or stream when copying from a stream'
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp from container to stdout" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-stdout
+ mkdir -p $srcdir
+ rand_content=$(random_string 50)
+
+ run_podman run -d --name cpcontainer $IMAGE sleep infinity
+
+ run_podman exec cpcontainer sh -c "echo '$rand_content' > /tmp/file.txt"
+ run_podman exec cpcontainer touch /tmp/empty.txt
+
+ # Copying from stdout will always compress. So let's copy the previously
+ # created file from the container via stdout, untar the archive and make
+ # sure the file exists with the expected content.
+ #
+ # NOTE that we can't use run_podman because that uses the BATS 'run'
+ # function which redirects stdout and stderr. Here we need to guarantee
+ # that podman's stdout is a pipe, not any other form of redirection.
+
+ # Copy file.
+ $PODMAN cp cpcontainer:/tmp/file.txt - > $srcdir/stdout.tar
+ if [ $? -ne 0 ]; then
+ die "Command failed: podman cp ... - | cat"
+ fi
+
+ tar xvf $srcdir/stdout.tar -C $srcdir
+ run cat $srcdir/file.txt
+ is "$output" "$rand_content"
+ run 1 ls $srcfir/empty.txt
+ rm -f $srcdir/*
+
+ # Copy directory.
+ $PODMAN cp cpcontainer:/tmp - > $srcdir/stdout.tar
+ if [ $? -ne 0 ]; then
+ die "Command failed: podman cp ... - | cat : $output"
+ fi
+
+ tar xvf $srcdir/stdout.tar -C $srcdir
+ run cat $srcdir/file.txt
+ is "$output" "$rand_content"
+ run cat $srcdir/empty.txt
+ is "$output" ""
+
+ run_podman rm -f cpcontainer
+}
+
function teardown() {
# In case any test fails, clean up the container we left behind
run_podman rm -f cpcontainer
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index 8ea9b1c69..272e2ae93 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -28,12 +28,15 @@ verify_iid_and_name() {
@test "podman save to pipe and load" {
# Generate a random name and tag (must be lower-case)
- local random_name=x$(random_string 12 | tr A-Z a-z)
- local random_tag=t$(random_string 7 | tr A-Z a-z)
+ local random_name=x0$(random_string 12 | tr A-Z a-z)
+ local random_tag=t0$(random_string 7 | tr A-Z a-z)
local fqin=localhost/$random_name:$random_tag
run_podman tag $IMAGE $fqin
- archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar
+ # Believe it or not, 'podman load' would barf if any path element
+ # included a capital letter
+ archive=$PODMAN_TMPDIR/MySubDirWithCaps/MyImage-$(random_string 8).tar
+ mkdir -p $(dirname $archive)
# We can't use run_podman because that uses the BATS 'run' function
# which redirects stdout and stderr. Here we need to guarantee
@@ -51,19 +54,20 @@ verify_iid_and_name() {
run_podman images $fqin --format '{{.Repository}}:{{.Tag}}'
is "$output" "$fqin" "image preserves name across save/load"
- # FIXME: when/if 7337 gets fixed, load with a new tag
- if false; then
- local new_name=x$(random_string 14 | tr A-Z a-z)
- local new_tag=t$(random_string 6 | tr A-Z a-z)
+ # Load with a new tag
+ local new_name=x1$(random_string 14 | tr A-Z a-z)
+ local new_tag=t1$(random_string 6 | tr A-Z a-z)
run_podman rmi $fqin
- fqin=localhost/$new_name:$new_tag
- run_podman load -i $archive $fqin
- run_podman images $fqin --format '{{.Repository}}:{{.Tag}}'
- is "$output" "$fqin" "image can be loaded with new name:tag"
- fi
+
+ new_fqin=localhost/$new_name:$new_tag
+ run_podman load -i $archive $new_fqin
+ run_podman images --format '{{.Repository}}:{{.Tag}}' --sort tag
+ is "${lines[0]}" "$IMAGE" "image is preserved"
+ is "${lines[1]}" "$fqin" "image is reloaded with old fqin"
+ is "${lines[2]}" "$new_fqin" "image is reloaded with new fqin too"
# Clean up
- run_podman rmi $fqin
+ run_podman rmi $fqin $new_fqin
}
diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats
index 142d7dcd9..20fdd068f 100644
--- a/test/system/400-unprivileged-access.bats
+++ b/test/system/400-unprivileged-access.bats
@@ -118,7 +118,7 @@ EOF
/proc/scsi
/sys/firmware
/sys/fs/selinux
- /sys/dev
+ /sys/dev/block
)
# Some of the above may not exist on our host. Find only the ones that do.
diff --git a/test/utils/podmansession_test.go b/test/utils/podmansession_test.go
index d0e2f3d06..763cb4f26 100644
--- a/test/utils/podmansession_test.go
+++ b/test/utils/podmansession_test.go
@@ -53,8 +53,8 @@ var _ = Describe("PodmanSession test", func() {
})
It("Test LineInOutputStartsWith", func() {
- Expect(session.LineInOuputStartsWith("Podman")).To(BeTrue())
- Expect(session.LineInOuputStartsWith("Session")).To(Not(BeTrue()))
+ Expect(session.LineInOutputStartsWith("Podman")).To(BeTrue())
+ Expect(session.LineInOutputStartsWith("Session")).To(Not(BeTrue()))
})
It("Test LineInOutputContains", func() {
diff --git a/test/utils/utils.go b/test/utils/utils.go
index dd836f258..d08939678 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -277,7 +277,7 @@ func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) {
// LineInOutputStartsWith returns true if a line in a
// session output starts with the supplied string
-func (s *PodmanSession) LineInOuputStartsWith(term string) bool {
+func (s *PodmanSession) LineInOutputStartsWith(term string) bool {
for _, i := range s.OutputToStringArray() {
if strings.HasPrefix(i, term) {
return true
diff --git a/troubleshooting.md b/troubleshooting.md
index 3ff578142..78e22fa2f 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -248,7 +248,10 @@ This means johndoe is allocated UIDS 100000-165535 as well as his standard UID i
/etc/passwd file.
You should ensure that each user has a unique range of uids, because overlapping UIDs,
-would potentially allow one user to attack another user.
+would potentially allow one user to attack another user. In addition, make sure
+that the range of uids you allocate can cover all uids that the container
+requires. For example, if the container has a user with uid 10000, ensure you
+have at least 10001 subuids.
You could also use the usermod program to assign UIDs to a user.
diff --git a/utils/utils_supported.go b/utils/utils_supported.go
index bcaa2c61a..6f517dc72 100644
--- a/utils/utils_supported.go
+++ b/utils/utils_supported.go
@@ -43,6 +43,15 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error {
ch := make(chan string)
_, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
if err != nil {
+ // On errors check if the cgroup already exists, if it does move the process there
+ if props, err := conn.GetUnitTypeProperties(unitName, "Scope"); err == nil {
+ if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" {
+ if err := moveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err != nil {
+ return err
+ }
+ return nil
+ }
+ }
return err
}
defer conn.Close()
@@ -101,6 +110,13 @@ func GetCgroupProcess(pid int) (string, error) {
// MoveUnderCgroupSubtree moves the PID under a cgroup subtree.
func MoveUnderCgroupSubtree(subtree string) error {
+ return moveUnderCgroup("", subtree, nil)
+}
+
+// moveUnderCgroup moves a group of processes to a new cgroup.
+// If cgroup is the empty string, then the current calling process cgroup is used.
+// If processes is empty, then the processes from the current cgroup are moved.
+func moveUnderCgroup(cgroup, subtree string, processes []uint32) error {
procFile := "/proc/self/cgroup"
f, err := os.Open(procFile)
if err != nil {
@@ -140,13 +156,12 @@ func MoveUnderCgroupSubtree(subtree string) error {
cgroupRoot = filepath.Join(cgroupRoot, controller)
}
- processes, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs"))
- if err != nil {
- return err
+ parentCgroup := cgroup
+ if parentCgroup == "" {
+ parentCgroup = parts[2]
}
-
- newCgroup := filepath.Join(cgroupRoot, parts[2], subtree)
- if err := os.Mkdir(newCgroup, 0755); err != nil {
+ newCgroup := filepath.Join(cgroupRoot, parentCgroup, subtree)
+ if err := os.Mkdir(newCgroup, 0755); err != nil && !os.IsExist(err) {
return err
}
@@ -156,9 +171,21 @@ func MoveUnderCgroupSubtree(subtree string) error {
}
defer f.Close()
- for _, pid := range bytes.Split(processes, []byte("\n")) {
- if _, err := f.Write(pid); err != nil {
- logrus.Warnf("Cannot move process %s to cgroup %q", pid, newCgroup)
+ if len(processes) > 0 {
+ for _, pid := range processes {
+ if _, err := f.Write([]byte(fmt.Sprintf("%d\n", pid))); err != nil {
+ logrus.Warnf("Cannot move process %d to cgroup %q", pid, newCgroup)
+ }
+ }
+ } else {
+ processesData, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs"))
+ if err != nil {
+ return err
+ }
+ for _, pid := range bytes.Split(processesData, []byte("\n")) {
+ if _, err := f.Write(pid); err != nil {
+ logrus.Warnf("Cannot move process %s to cgroup %q", string(pid), newCgroup)
+ }
}
}
}
diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go
index 2769781f2..320d5e0e5 100644
--- a/vendor/github.com/containers/common/pkg/config/config.go
+++ b/vendor/github.com/containers/common/pkg/config/config.go
@@ -268,6 +268,10 @@ type EngineConfig struct {
// NetworkCmdPath is the path to the slirp4netns binary.
NetworkCmdPath string `toml:"network_cmd_path,omitempty"`
+ // NetworkCmdOptions is the default options to pass to the slirp4netns binary.
+ // For example "allow_host_loopback=true"
+ NetworkCmdOptions []string `toml:"network_cmd_options,omitempty"`
+
// NoPivotRoot sets whether to set no-pivot-root in the OCI runtime.
NoPivotRoot bool `toml:"no_pivot_root,omitempty"`
@@ -359,6 +363,12 @@ type EngineConfig struct {
// under. This convention is followed by the default volume driver, but
// may not be by other drivers.
VolumePath string `toml:"volume_path,omitempty"`
+
+ // VolumePlugins is a set of plugins that can be used as the backend for
+ // Podman named volumes. Each volume is specified as a name (what Podman
+ // will refer to the plugin as) mapped to a path, which must point to a
+ // Unix socket that conforms to the Volume Plugin specification.
+ VolumePlugins map[string]string `toml:"volume_plugins,omitempty"`
}
// SetOptions contains a subset of options in a Config. It's used to indicate if
@@ -441,11 +451,6 @@ func NewConfig(userConfigPath string) (*Config, error) {
return nil, err
}
- // read libpod.conf and convert the config to *Config
- if err = newLibpodConfig(config); err != nil && !os.IsNotExist(err) {
- logrus.Errorf("error reading libpod.conf: %v", err)
- }
-
// Now, gather the system configs and merge them as needed.
configs, err := systemConfigs()
if err != nil {
diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf
index ed7c91931..12fbecc22 100644
--- a/vendor/github.com/containers/common/pkg/config/containers.conf
+++ b/vendor/github.com/containers/common/pkg/config/containers.conf
@@ -348,6 +348,11 @@ default_sysctls = [
#
# network_cmd_path=""
+# Default options to pass to the slirp4netns binary.
+# For example "allow_host_loopback=true"
+#
+# network_cmd_options=[]
+
# Whether to use chroot instead of pivot_root in the runtime
#
# no_pivot_root = false
@@ -386,7 +391,7 @@ default_sysctls = [
# Default OCI runtime
#
-# runtime = "runc"
+# runtime = "crun"
# List of the OCI runtimes that support --format=json. When json is supported
# engine will use it for reporting nicer errors.
@@ -453,8 +458,11 @@ default_sysctls = [
# "/usr/bin/kata-fc",
# ]
-# The [engine.runtimes] table MUST be the last entry in this file.
+[engine.volume_plugins]
+# testplugin = "/run/podman/plugins/test.sock"
+
+# The [engine.volume_plugins] table MUST be the last entry in this file.
# (Unless another table is added)
# TOML does not provide a way to end a table other than a further table being
-# defined, so every key hereafter will be part of [runtimes] and not the main
-# config.
+# defined, so every key hereafter will be part of [volume_plugins] and not the
+# main config.
diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go
index 4f1460e3b..2b3a098a7 100644
--- a/vendor/github.com/containers/common/pkg/config/default.go
+++ b/vendor/github.com/containers/common/pkg/config/default.go
@@ -242,11 +242,7 @@ func defaultConfigFromMemory() (*EngineConfig, error) {
c.ImageDefaultTransport = _defaultTransport
c.StateType = BoltDBStateStore
- c.OCIRuntime = "runc"
- // If we're running on cgroupv2 v2, default to using crun.
- if cgroup2, _ := cgroupv2.Enabled(); cgroup2 {
- c.OCIRuntime = "crun"
- }
+ c.OCIRuntime = "crun"
c.ImageBuildFormat = "oci"
c.CgroupManager = defaultCgroupManager()
diff --git a/vendor/github.com/containers/common/pkg/config/libpodConfig.go b/vendor/github.com/containers/common/pkg/config/libpodConfig.go
deleted file mode 100644
index 2df3d6077..000000000
--- a/vendor/github.com/containers/common/pkg/config/libpodConfig.go
+++ /dev/null
@@ -1,407 +0,0 @@
-package config
-
-/* libpodConfig.go contains deprecated functionality and should not be used any longer */
-
-import (
- "os"
- "os/exec"
- "path/filepath"
-
- "github.com/BurntSushi/toml"
- "github.com/containers/common/pkg/cgroupv2"
- "github.com/containers/storage/pkg/unshare"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-const (
- // _rootlessConfigPath is the path to the rootless libpod.conf in $HOME.
- _rootlessConfigPath = ".config/containers/libpod.conf"
-
- // _rootConfigPath is the path to the libpod configuration file
- // This file is loaded to replace the builtin default config before
- // runtime options (e.g. WithStorageConfig) are applied.
- // If it is not present, the builtin default config is used instead
- // This path can be overridden when the runtime is created by using
- // NewRuntimeFromConfig() instead of NewRuntime().
- _rootConfigPath = _installPrefix + "/share/containers/libpod.conf"
-
- // _rootOverrideConfigPath is the path to an override for the default libpod
- // configuration file. If OverrideConfigPath exists, it will be used in
- // place of the configuration file pointed to by ConfigPath.
- _rootOverrideConfigPath = _etcDir + "/containers/libpod.conf"
-)
-
-// ConfigFromLibpod contains configuration options used to set up a libpod runtime
-type ConfigFromLibpod struct {
- // NOTE: when changing this struct, make sure to update (*Config).Merge().
-
- // SetOptions contains a subset of config options. It's used to indicate if
- // a given option has either been set by the user or by a parsed libpod
- // configuration file. If not, the corresponding option might be
- // overwritten by values from the database. This behavior guarantees
- // backwards compat with older version of libpod and Podman.
- SetOptions
-
- // VolumePath is the default location that named volumes will be created
- // under. This convention is followed by the default volume driver, but
- // may not be by other drivers.
- VolumePath string `toml:"volume_path,omitempty"`
-
- // ImageDefaultTransport is the default transport method used to fetch
- // images.
- ImageDefaultTransport string `toml:"image_default_transport,omitempty"`
-
- // SignaturePolicyPath is the path to a signature policy to use for
- // validating images. If left empty, the containers/image default signature
- // policy will be used.
- SignaturePolicyPath string `toml:"signature_policy_path,omitempty"`
-
- // OCIRuntime is the OCI runtime to use.
- OCIRuntime string `toml:"runtime,omitempty"`
-
- // OCIRuntimes are the set of configured OCI runtimes (default is runc).
- OCIRuntimes map[string][]string `toml:"runtimes,omitempty"`
-
- // RuntimeSupportsJSON is the list of the OCI runtimes that support
- // --format=json.
- RuntimeSupportsJSON []string `toml:"runtime_supports_json,omitempty"`
-
- // RuntimeSupportsNoCgroups is a list of OCI runtimes that support
- // running containers without CGroups.
- RuntimeSupportsNoCgroups []string `toml:"runtime_supports_nocgroupv2,omitempty"`
-
- // RuntimePath is the path to OCI runtime binary for launching containers.
- // The first path pointing to a valid file will be used This is used only
- // when there are no OCIRuntime/OCIRuntimes defined. It is used only to be
- // backward compatible with older versions of Podman.
- RuntimePath []string `toml:"runtime_path,omitempty"`
-
- // ConmonPath is the path to the Conmon binary used for managing containers.
- // The first path pointing to a valid file will be used.
- ConmonPath []string `toml:"conmon_path,omitempty"`
-
- // ConmonEnvVars are environment variables to pass to the Conmon binary
- // when it is launched.
- ConmonEnvVars []string `toml:"conmon_env_vars,omitempty"`
-
- // CGroupManager is the CGroup Manager to use Valid values are "cgroupfs"
- // and "systemd".
- CgroupManager string `toml:"cgroup_manager,omitempty"`
-
- // InitPath is the path to the container-init binary.
- InitPath string `toml:"init_path,omitempty"`
-
- // StaticDir is the path to a persistent directory to store container
- // files.
- StaticDir string `toml:"static_dir,omitempty"`
-
- // TmpDir is the path to a temporary directory to store per-boot container
- // files. Must be stored in a tmpfs.
- TmpDir string `toml:"tmp_dir,omitempty"`
-
- // MaxLogSize is the maximum size of container logfiles.
- MaxLogSize int64 `toml:"max_log_size,omitempty"`
-
- // NoPivotRoot sets whether to set no-pivot-root in the OCI runtime.
- NoPivotRoot bool `toml:"no_pivot_root,omitempty"`
-
- // CNIConfigDir sets the directory where CNI configuration files are
- // stored.
- CNIConfigDir string `toml:"cni_config_dir,omitempty"`
-
- // CNIPluginDir sets a number of directories where the CNI network
- // plugins can be located.
- CNIPluginDir []string `toml:"cni_plugin_dir,omitempty"`
-
- // CNIDefaultNetwork is the network name of the default CNI network
- // to attach pods to.
- CNIDefaultNetwork string `toml:"cni_default_network,omitempty"`
-
- // HooksDir holds paths to the directories containing hooks
- // configuration files. When the same filename is present in in
- // multiple directories, the file in the directory listed last in
- // this slice takes precedence.
- HooksDir []string `toml:"hooks_dir,omitempty"`
-
- // Namespace is the libpod namespace to use. Namespaces are used to create
- // scopes to separate containers and pods in the state. When namespace is
- // set, libpod will only view containers and pods in the same namespace. All
- // containers and pods created will default to the namespace set here. A
- // namespace of "", the empty string, is equivalent to no namespace, and all
- // containers and pods will be visible. The default namespace is "".
- Namespace string `toml:"namespace,omitempty"`
-
- // InfraImage is the image a pod infra container will use to manage
- // namespaces.
- InfraImage string `toml:"infra_image,omitempty"`
-
- // InfraCommand is the command run to start up a pod infra container.
- InfraCommand string `toml:"infra_command,omitempty"`
-
- // EnablePortReservation determines whether libpod will reserve ports on the
- // host when they are forwarded to containers. When enabled, when ports are
- // forwarded to containers, they are held open by conmon as long as the
- // container is running, ensuring that they cannot be reused by other
- // programs on the host. However, this can cause significant memory usage if
- // a container has many ports forwarded to it. Disabling this can save
- // memory.
- EnablePortReservation bool `toml:"enable_port_reservation,omitempty"`
-
- // EnableLabeling indicates whether libpod will support container labeling.
- EnableLabeling bool `toml:"label,omitempty"`
-
- // NetworkCmdPath is the path to the slirp4netns binary.
- NetworkCmdPath string `toml:"network_cmd_path,omitempty"`
-
- // NumLocks is the number of locks to make available for containers and
- // pods.
- NumLocks uint32 `toml:"num_locks,omitempty"`
-
- // LockType is the type of locking to use.
- LockType string `toml:"lock_type,omitempty"`
-
- // EventsLogger determines where events should be logged.
- EventsLogger string `toml:"events_logger,omitempty"`
-
- // EventsLogFilePath is where the events log is stored.
- EventsLogFilePath string `toml:"events_logfile_path,omitempty"`
-
- // DetachKeys is the sequence of keys used to detach a container.
- DetachKeys string `toml:"detach_keys,omitempty"`
-
- // SDNotify tells Libpod to allow containers to notify the host systemd of
- // readiness using the SD_NOTIFY mechanism.
- SDNotify bool `toml:",omitempty"`
-
- // CgroupCheck indicates the configuration has been rewritten after an
- // upgrade to Fedora 31 to change the default OCI runtime for cgroupv2v2.
- CgroupCheck bool `toml:"cgroup_check,omitempty"`
-}
-
-// newLibpodConfig creates a new ConfigFromLibpod and converts it to Config.
-// Depending if we're running as root or rootless, we then merge the system configuration followed
-// by merging the default config (hard-coded default in memory).
-// Note that the OCI runtime is hard-set to `crun` if we're running on a system
-// with cgroupv2v2. Other OCI runtimes are not yet supporting cgroupv2v2. This
-// might change in the future.
-func newLibpodConfig(c *Config) error {
- // Start with the default config and iteratively merge
- // fields in the system configs.
- config := c.libpodConfig()
-
- // Now, check if the user can access system configs and merge them if needed.
- configs, err := systemLibpodConfigs()
- if err != nil {
- return errors.Wrapf(err, "error finding config on system")
- }
-
- if len(configs) == 0 {
- return nil
- }
-
- for _, path := range configs {
- config, err = readLibpodConfigFromFile(path, config)
- if err != nil {
- return errors.Wrapf(err, "error reading system config %q", path)
- }
- }
-
- // Since runc does not currently support cgroupV2
- // Change to default crun on first running of libpod.conf
- // TODO Once runc has support for cgroupv2, this function should be removed.
- if !config.CgroupCheck && unshare.IsRootless() {
- cgroup2, err := cgroupv2.Enabled()
- if err != nil {
- return err
- }
- if cgroup2 {
- path, err := exec.LookPath("crun")
- if err != nil {
- // Can't find crun path so do nothing
- logrus.Warnf("Can not find crun package on the host, containers might fail to run on cgroup V2 systems without crun: %q", err)
- } else {
- config.CgroupCheck = true
- config.OCIRuntime = path
- }
- }
- }
-
- // hard code EventsLogger to "file" to match older podman versions.
- if config.EventsLogger != "file" {
- logrus.Warnf("Ignoring libpod.conf EventsLogger setting %q. Use %q if you want to change this setting and remove libpod.conf files.", config.EventsLogger, Path())
- config.EventsLogger = "file"
- }
-
- c.libpodToContainersConfig(config)
-
- return nil
-}
-
-// readConfigFromFile reads the specified config file at `path` and attempts to
-// unmarshal its content into a Config. The config param specifies the previous
-// default config. If the path, only specifies a few fields in the Toml file
-// the defaults from the config parameter will be used for all other fields.
-func readLibpodConfigFromFile(path string, config *ConfigFromLibpod) (*ConfigFromLibpod, error) {
- logrus.Debugf("Reading configuration file %q", path)
- _, err := toml.DecodeFile(path, config)
- if err != nil {
- return nil, errors.Wrapf(err, "decode configuration %s", path)
- }
-
- return config, err
-}
-
-func systemLibpodConfigs() ([]string, error) {
- if unshare.IsRootless() {
- path, err := rootlessLibpodConfigPath()
- if err != nil {
- return nil, err
- }
- if _, err := os.Stat(path); err == nil {
- containersConfPath, err := rootlessConfigPath()
- if err != nil {
- containersConfPath = filepath.Join("$HOME", UserOverrideContainersConfig)
- }
- logrus.Warnf("Found deprecated file %s, please remove. Use %s to override defaults.\n", path, containersConfPath)
- return []string{path}, nil
- }
- return nil, err
- }
-
- configs := []string{}
- if _, err := os.Stat(_rootConfigPath); err == nil {
- logrus.Warnf("Found deprecated file %s, please remove. Use %s to override defaults.\n", _rootConfigPath, OverrideContainersConfig)
- configs = append(configs, _rootConfigPath)
- }
- if _, err := os.Stat(_rootOverrideConfigPath); err == nil {
- logrus.Warnf("Found deprecated file %s, please remove. Use %s to override defaults.\n", _rootOverrideConfigPath, OverrideContainersConfig)
- configs = append(configs, _rootOverrideConfigPath)
- }
- return configs, nil
-}
-
-func rootlessLibpodConfigPath() (string, error) {
- home, err := unshare.HomeDir()
- if err != nil {
- return "", err
- }
-
- return filepath.Join(home, _rootlessConfigPath), nil
-}
-
-func (c *Config) libpodConfig() *ConfigFromLibpod {
- return &ConfigFromLibpod{
- InitPath: c.Containers.InitPath,
- MaxLogSize: c.Containers.LogSizeMax,
- EnableLabeling: c.Containers.EnableLabeling,
-
- SetOptions: c.Engine.SetOptions,
- VolumePath: c.Engine.VolumePath,
- ImageDefaultTransport: c.Engine.ImageDefaultTransport,
- OCIRuntime: c.Engine.OCIRuntime,
- OCIRuntimes: c.Engine.OCIRuntimes,
- RuntimeSupportsJSON: c.Engine.RuntimeSupportsJSON,
- RuntimeSupportsNoCgroups: c.Engine.RuntimeSupportsNoCgroups,
- RuntimePath: c.Engine.RuntimePath,
- ConmonPath: c.Engine.ConmonPath,
- ConmonEnvVars: c.Engine.ConmonEnvVars,
- CgroupManager: c.Engine.CgroupManager,
- StaticDir: c.Engine.StaticDir,
- TmpDir: c.Engine.TmpDir,
- NoPivotRoot: c.Engine.NoPivotRoot,
- HooksDir: c.Engine.HooksDir,
- Namespace: c.Engine.Namespace,
- InfraImage: c.Engine.InfraImage,
- InfraCommand: c.Engine.InfraCommand,
- EnablePortReservation: c.Engine.EnablePortReservation,
- NetworkCmdPath: c.Engine.NetworkCmdPath,
- NumLocks: c.Engine.NumLocks,
- LockType: c.Engine.LockType,
- EventsLogger: c.Engine.EventsLogger,
- EventsLogFilePath: c.Engine.EventsLogFilePath,
- DetachKeys: c.Engine.DetachKeys,
- SDNotify: c.Engine.SDNotify,
- CgroupCheck: c.Engine.CgroupCheck,
- SignaturePolicyPath: c.Engine.SignaturePolicyPath,
-
- CNIConfigDir: c.Network.NetworkConfigDir,
- CNIPluginDir: c.Network.CNIPluginDirs,
- CNIDefaultNetwork: c.Network.DefaultNetwork,
- }
-}
-
-func (c *Config) libpodToContainersConfig(libpodConf *ConfigFromLibpod) {
-
- if libpodConf.InitPath != "" {
- c.Containers.InitPath = libpodConf.InitPath
- }
- c.Containers.LogSizeMax = libpodConf.MaxLogSize
- c.Containers.EnableLabeling = libpodConf.EnableLabeling
-
- if libpodConf.SignaturePolicyPath != "" {
- c.Engine.SignaturePolicyPath = libpodConf.SignaturePolicyPath
- }
- c.Engine.SetOptions = libpodConf.SetOptions
- if libpodConf.VolumePath != "" {
- c.Engine.VolumePath = libpodConf.VolumePath
- }
- if libpodConf.ImageDefaultTransport != "" {
- c.Engine.ImageDefaultTransport = libpodConf.ImageDefaultTransport
- }
- if libpodConf.OCIRuntime != "" {
- c.Engine.OCIRuntime = libpodConf.OCIRuntime
- }
- c.Engine.OCIRuntimes = libpodConf.OCIRuntimes
- c.Engine.RuntimeSupportsJSON = libpodConf.RuntimeSupportsJSON
- c.Engine.RuntimeSupportsNoCgroups = libpodConf.RuntimeSupportsNoCgroups
- c.Engine.RuntimePath = libpodConf.RuntimePath
- c.Engine.ConmonPath = libpodConf.ConmonPath
- c.Engine.ConmonEnvVars = libpodConf.ConmonEnvVars
- if libpodConf.CgroupManager != "" {
- c.Engine.CgroupManager = libpodConf.CgroupManager
- }
- if libpodConf.StaticDir != "" {
- c.Engine.StaticDir = libpodConf.StaticDir
- }
- if libpodConf.TmpDir != "" {
- c.Engine.TmpDir = libpodConf.TmpDir
- }
- c.Engine.NoPivotRoot = libpodConf.NoPivotRoot
- c.Engine.HooksDir = libpodConf.HooksDir
- if libpodConf.Namespace != "" {
- c.Engine.Namespace = libpodConf.Namespace
- }
- if libpodConf.InfraImage != "" {
- c.Engine.InfraImage = libpodConf.InfraImage
- }
- if libpodConf.InfraCommand != "" {
- c.Engine.InfraCommand = libpodConf.InfraCommand
- }
-
- c.Engine.EnablePortReservation = libpodConf.EnablePortReservation
- if libpodConf.NetworkCmdPath != "" {
- c.Engine.NetworkCmdPath = libpodConf.NetworkCmdPath
- }
- c.Engine.NumLocks = libpodConf.NumLocks
- c.Engine.LockType = libpodConf.LockType
- if libpodConf.EventsLogger != "" {
- c.Engine.EventsLogger = libpodConf.EventsLogger
- }
- if libpodConf.EventsLogFilePath != "" {
- c.Engine.EventsLogFilePath = libpodConf.EventsLogFilePath
- }
- if libpodConf.DetachKeys != "" {
- c.Engine.DetachKeys = libpodConf.DetachKeys
- }
- c.Engine.SDNotify = libpodConf.SDNotify
- c.Engine.CgroupCheck = libpodConf.CgroupCheck
-
- if libpodConf.CNIConfigDir != "" {
- c.Network.NetworkConfigDir = libpodConf.CNIConfigDir
- }
- c.Network.CNIPluginDirs = libpodConf.CNIPluginDir
- if libpodConf.CNIDefaultNetwork != "" {
- c.Network.DefaultNetwork = libpodConf.CNIDefaultNetwork
- }
-}
diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go
index 72f4e00f7..8df453484 100644
--- a/vendor/github.com/containers/common/version/version.go
+++ b/vendor/github.com/containers/common/version/version.go
@@ -1,4 +1,4 @@
package version
// Version is the version of the build.
-const Version = "0.29.0"
+const Version = "0.31.0"
diff --git a/vendor/github.com/coreos/go-systemd/LICENSE b/vendor/github.com/coreos/go-systemd/LICENSE
new file mode 100644
index 000000000..37ec93a14
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/coreos/go-systemd/NOTICE b/vendor/github.com/coreos/go-systemd/NOTICE
new file mode 100644
index 000000000..23a0ada2f
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/NOTICE
@@ -0,0 +1,5 @@
+CoreOS Project
+Copyright 2018 CoreOS, Inc
+
+This product includes software developed at CoreOS, Inc.
+(http://www.coreos.com/).
diff --git a/vendor/github.com/coreos/go-systemd/activation/files.go b/vendor/github.com/coreos/go-systemd/activation/files.go
new file mode 100644
index 000000000..29dd18def
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/activation/files.go
@@ -0,0 +1,67 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package activation implements primitives for systemd socket activation.
+package activation
+
+import (
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+)
+
+const (
+ // listenFdsStart corresponds to `SD_LISTEN_FDS_START`.
+ listenFdsStart = 3
+)
+
+// Files returns a slice containing a `os.File` object for each
+// file descriptor passed to this process via systemd fd-passing protocol.
+//
+// The order of the file descriptors is preserved in the returned slice.
+// `unsetEnv` is typically set to `true` in order to avoid clashes in
+// fd usage and to avoid leaking environment flags to child processes.
+func Files(unsetEnv bool) []*os.File {
+ if unsetEnv {
+ defer os.Unsetenv("LISTEN_PID")
+ defer os.Unsetenv("LISTEN_FDS")
+ defer os.Unsetenv("LISTEN_FDNAMES")
+ }
+
+ pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
+ if err != nil || pid != os.Getpid() {
+ return nil
+ }
+
+ nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
+ if err != nil || nfds == 0 {
+ return nil
+ }
+
+ names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":")
+
+ files := make([]*os.File, 0, nfds)
+ for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
+ syscall.CloseOnExec(fd)
+ name := "LISTEN_FD_" + strconv.Itoa(fd)
+ offset := fd - listenFdsStart
+ if offset < len(names) && len(names[offset]) > 0 {
+ name = names[offset]
+ }
+ files = append(files, os.NewFile(uintptr(fd), name))
+ }
+
+ return files
+}
diff --git a/vendor/github.com/coreos/go-systemd/activation/listeners.go b/vendor/github.com/coreos/go-systemd/activation/listeners.go
new file mode 100644
index 000000000..bb5cc2311
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/activation/listeners.go
@@ -0,0 +1,103 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package activation
+
+import (
+ "crypto/tls"
+ "net"
+)
+
+// Listeners returns a slice containing a net.Listener for each matching socket type
+// passed to this process.
+//
+// The order of the file descriptors is preserved in the returned slice.
+// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
+// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
+func Listeners() ([]net.Listener, error) {
+ files := Files(true)
+ listeners := make([]net.Listener, len(files))
+
+ for i, f := range files {
+ if pc, err := net.FileListener(f); err == nil {
+ listeners[i] = pc
+ f.Close()
+ }
+ }
+ return listeners, nil
+}
+
+// ListenersWithNames maps a listener name to a set of net.Listener instances.
+func ListenersWithNames() (map[string][]net.Listener, error) {
+ files := Files(true)
+ listeners := map[string][]net.Listener{}
+
+ for _, f := range files {
+ if pc, err := net.FileListener(f); err == nil {
+ current, ok := listeners[f.Name()]
+ if !ok {
+ listeners[f.Name()] = []net.Listener{pc}
+ } else {
+ listeners[f.Name()] = append(current, pc)
+ }
+ f.Close()
+ }
+ }
+ return listeners, nil
+}
+
+// TLSListeners returns a slice containing a net.listener for each matching TCP socket type
+// passed to this process.
+// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig.
+func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
+ listeners, err := Listeners()
+
+ if listeners == nil || err != nil {
+ return nil, err
+ }
+
+ if tlsConfig != nil && err == nil {
+ for i, l := range listeners {
+ // Activate TLS only for TCP sockets
+ if l.Addr().Network() == "tcp" {
+ listeners[i] = tls.NewListener(l, tlsConfig)
+ }
+ }
+ }
+
+ return listeners, err
+}
+
+// TLSListenersWithNames maps a listener name to a net.Listener with
+// the associated TLS configuration.
+func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) {
+ listeners, err := ListenersWithNames()
+
+ if listeners == nil || err != nil {
+ return nil, err
+ }
+
+ if tlsConfig != nil && err == nil {
+ for _, ll := range listeners {
+ // Activate TLS only for TCP sockets
+ for i, l := range ll {
+ if l.Addr().Network() == "tcp" {
+ ll[i] = tls.NewListener(l, tlsConfig)
+ }
+ }
+ }
+ }
+
+ return listeners, err
+}
diff --git a/vendor/github.com/coreos/go-systemd/activation/packetconns.go b/vendor/github.com/coreos/go-systemd/activation/packetconns.go
new file mode 100644
index 000000000..a97206785
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/activation/packetconns.go
@@ -0,0 +1,38 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package activation
+
+import (
+ "net"
+)
+
+// PacketConns returns a slice containing a net.PacketConn for each matching socket type
+// passed to this process.
+//
+// The order of the file descriptors is preserved in the returned slice.
+// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
+// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
+func PacketConns() ([]net.PacketConn, error) {
+ files := Files(true)
+ conns := make([]net.PacketConn, len(files))
+
+ for i, f := range files {
+ if pc, err := net.FilePacketConn(f); err == nil {
+ conns[i] = pc
+ f.Close()
+ }
+ }
+ return conns, nil
+}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
index ee09adc8d..7a9f97d1c 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
@@ -149,7 +149,7 @@ func (plugin *cniNetworkPlugin) monitorConfDir(start *sync.WaitGroup) {
for {
select {
case event := <-plugin.watcher.Events:
- logrus.Warningf("CNI monitoring event %v", event)
+ logrus.Infof("CNI monitoring event %v", event)
var defaultDeleted bool
createWrite := (event.Op&fsnotify.Create == fsnotify.Create ||
@@ -295,7 +295,7 @@ func loadNetworks(confDir string, cni *libcni.CNIConfig) (map[string]*cniNetwork
}
}
if len(confList.Plugins) == 0 {
- logrus.Warningf("CNI config list %s has no networks, skipping", confFile)
+ logrus.Infof("CNI config list %s has no networks, skipping", confFile)
continue
}
@@ -350,7 +350,7 @@ func (plugin *cniNetworkPlugin) syncNetworkConfig() error {
plugin.defaultNetName.name = defaultNetName
logrus.Infof("Update default CNI network name to %s", defaultNetName)
} else {
- logrus.Warnf("Default CNI network name %s is unchangeable", plugin.defaultNetName.name)
+ logrus.Debugf("Default CNI network name %s is unchangeable", plugin.defaultNetName.name)
}
plugin.networks = networks
diff --git a/vendor/github.com/docker/go-plugins-helpers/LICENSE b/vendor/github.com/docker/go-plugins-helpers/LICENSE
new file mode 100644
index 000000000..8f71f43fe
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/github.com/docker/go-plugins-helpers/NOTICE b/vendor/github.com/docker/go-plugins-helpers/NOTICE
new file mode 100644
index 000000000..6e6f469ab
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/NOTICE
@@ -0,0 +1,19 @@
+Docker
+Copyright 2012-2015 Docker, Inc.
+
+This product includes software developed at Docker, Inc. (https://www.docker.com).
+
+This product contains software (https://github.com/kr/pty) developed
+by Keith Rarick, licensed under the MIT License.
+
+The following is courtesy of our legal counsel:
+
+
+Use and transfer of Docker may be subject to certain restrictions by the
+United States and other governments.
+It is your responsibility to ensure that your use and/or transfer does not
+violate applicable laws.
+
+For more information, please see https://www.bis.doc.gov
+
+See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go b/vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go
new file mode 100644
index 000000000..195812a44
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go
@@ -0,0 +1,37 @@
+package sdk
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+// DefaultContentTypeV1_1 is the default content type accepted and sent by the plugins.
+const DefaultContentTypeV1_1 = "application/vnd.docker.plugins.v1.1+json"
+
+// DecodeRequest decodes an http request into a given structure.
+func DecodeRequest(w http.ResponseWriter, r *http.Request, req interface{}) (err error) {
+ if err = json.NewDecoder(r.Body).Decode(req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ }
+ return
+}
+
+// EncodeResponse encodes the given structure into an http response.
+func EncodeResponse(w http.ResponseWriter, res interface{}, err bool) {
+ w.Header().Set("Content-Type", DefaultContentTypeV1_1)
+ if err {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ json.NewEncoder(w).Encode(res)
+}
+
+// StreamResponse streams a response object to the client
+func StreamResponse(w http.ResponseWriter, data io.ReadCloser) {
+ w.Header().Set("Content-Type", DefaultContentTypeV1_1)
+ if _, err := copyBuf(w, data); err != nil {
+ fmt.Printf("ERROR in stream: %v\n", err)
+ }
+ data.Close()
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/handler.go b/vendor/github.com/docker/go-plugins-helpers/sdk/handler.go
new file mode 100644
index 000000000..c0d042ed0
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/handler.go
@@ -0,0 +1,88 @@
+package sdk
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net"
+ "net/http"
+ "os"
+)
+
+const activatePath = "/Plugin.Activate"
+
+// Handler is the base to create plugin handlers.
+// It initializes connections and sockets to listen to.
+type Handler struct {
+ mux *http.ServeMux
+}
+
+// NewHandler creates a new Handler with an http mux.
+func NewHandler(manifest string) Handler {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc(activatePath, func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", DefaultContentTypeV1_1)
+ fmt.Fprintln(w, manifest)
+ })
+
+ return Handler{mux: mux}
+}
+
+// Serve sets up the handler to serve requests on the passed in listener
+func (h Handler) Serve(l net.Listener) error {
+ server := http.Server{
+ Addr: l.Addr().String(),
+ Handler: h.mux,
+ }
+ return server.Serve(l)
+}
+
+// ServeTCP makes the handler to listen for request in a given TCP address.
+// It also writes the spec file in the right directory for docker to read.
+// Due to constrains for running Docker in Docker on Windows, data-root directory
+// of docker daemon must be provided. To get default directory, use
+// WindowsDefaultDaemonRootDir() function. On Unix, this parameter is ignored.
+func (h Handler) ServeTCP(pluginName, addr, daemonDir string, tlsConfig *tls.Config) error {
+ l, spec, err := newTCPListener(addr, pluginName, daemonDir, tlsConfig)
+ if err != nil {
+ return err
+ }
+ if spec != "" {
+ defer os.Remove(spec)
+ }
+ return h.Serve(l)
+}
+
+// ServeUnix makes the handler to listen for requests in a unix socket.
+// It also creates the socket file in the right directory for docker to read.
+func (h Handler) ServeUnix(addr string, gid int) error {
+ l, spec, err := newUnixListener(addr, gid)
+ if err != nil {
+ return err
+ }
+ if spec != "" {
+ defer os.Remove(spec)
+ }
+ return h.Serve(l)
+}
+
+// ServeWindows makes the handler to listen for request in a Windows named pipe.
+// It also creates the spec file in the right directory for docker to read.
+// Due to constrains for running Docker in Docker on Windows, data-root directory
+// of docker daemon must be provided. To get default directory, use
+// WindowsDefaultDaemonRootDir() function. On Unix, this parameter is ignored.
+func (h Handler) ServeWindows(addr, pluginName, daemonDir string, pipeConfig *WindowsPipeConfig) error {
+ l, spec, err := newWindowsListener(addr, pluginName, daemonDir, pipeConfig)
+ if err != nil {
+ return err
+ }
+ if spec != "" {
+ defer os.Remove(spec)
+ }
+ return h.Serve(l)
+}
+
+// HandleFunc registers a function to handle a request path with.
+func (h Handler) HandleFunc(path string, fn func(w http.ResponseWriter, r *http.Request)) {
+ h.mux.HandleFunc(path, fn)
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/pool.go b/vendor/github.com/docker/go-plugins-helpers/sdk/pool.go
new file mode 100644
index 000000000..316775973
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/pool.go
@@ -0,0 +1,18 @@
+package sdk
+
+import (
+ "io"
+ "sync"
+)
+
+const buffer32K = 32 * 1024
+
+var buffer32KPool = &sync.Pool{New: func() interface{} { return make([]byte, buffer32K) }}
+
+// copyBuf uses a shared buffer pool with io.CopyBuffer
+func copyBuf(w io.Writer, r io.Reader) (int64, error) {
+ buf := buffer32KPool.Get().([]byte)
+ written, err := io.CopyBuffer(w, r, buf)
+ buffer32KPool.Put(buf)
+ return written, err
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go b/vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go
new file mode 100644
index 000000000..bc8cfc644
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go
@@ -0,0 +1,58 @@
+package sdk
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+)
+
+type protocol string
+
+const (
+ protoTCP protocol = "tcp"
+ protoNamedPipe protocol = "npipe"
+)
+
+// PluginSpecDir returns plugin spec dir in relation to daemon root directory.
+func PluginSpecDir(daemonRoot string) string {
+ return ([]string{filepath.Join(daemonRoot, "plugins")})[0]
+}
+
+// WindowsDefaultDaemonRootDir returns default data directory of docker daemon on Windows.
+func WindowsDefaultDaemonRootDir() string {
+ return filepath.Join(os.Getenv("programdata"), "docker")
+}
+
+func createPluginSpecDirWindows(name, address, daemonRoot string) (string, error) {
+ _, err := os.Stat(daemonRoot)
+ if os.IsNotExist(err) {
+ return "", fmt.Errorf("Deamon root directory must already exist: %s", err)
+ }
+
+ pluginSpecDir := PluginSpecDir(daemonRoot)
+
+ if err := windowsCreateDirectoryWithACL(pluginSpecDir); err != nil {
+ return "", err
+ }
+ return pluginSpecDir, nil
+}
+
+func createPluginSpecDirUnix(name, address string) (string, error) {
+ pluginSpecDir := PluginSpecDir("/etc/docker")
+ if err := os.MkdirAll(pluginSpecDir, 0755); err != nil {
+ return "", err
+ }
+ return pluginSpecDir, nil
+}
+
+func writeSpecFile(name, address, pluginSpecDir string, proto protocol) (string, error) {
+ specFileDir := filepath.Join(pluginSpecDir, name+".spec")
+
+ url := string(proto) + "://" + address
+ if err := ioutil.WriteFile(specFileDir, []byte(url), 0644); err != nil {
+ return "", err
+ }
+
+ return specFileDir, nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go b/vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go
new file mode 100644
index 000000000..bad85f7fd
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go
@@ -0,0 +1,34 @@
+package sdk
+
+import (
+ "crypto/tls"
+ "net"
+ "runtime"
+
+ "github.com/docker/go-connections/sockets"
+)
+
+func newTCPListener(address, pluginName, daemonDir string, tlsConfig *tls.Config) (net.Listener, string, error) {
+ listener, err := sockets.NewTCPSocket(address, tlsConfig)
+ if err != nil {
+ return nil, "", err
+ }
+
+ addr := listener.Addr().String()
+
+ var specDir string
+ if runtime.GOOS == "windows" {
+ specDir, err = createPluginSpecDirWindows(pluginName, addr, daemonDir)
+ } else {
+ specDir, err = createPluginSpecDirUnix(pluginName, addr)
+ }
+ if err != nil {
+ return nil, "", err
+ }
+
+ specFile, err := writeSpecFile(pluginName, addr, specDir, protoTCP)
+ if err != nil {
+ return nil, "", err
+ }
+ return listener, specFile, nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go
new file mode 100644
index 000000000..54b9a6d31
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go
@@ -0,0 +1,35 @@
+// +build linux freebsd
+
+package sdk
+
+import (
+ "net"
+ "os"
+ "path/filepath"
+
+ "github.com/docker/go-connections/sockets"
+)
+
+const pluginSockDir = "/run/docker/plugins"
+
+func newUnixListener(pluginName string, gid int) (net.Listener, string, error) {
+ path, err := fullSocketAddress(pluginName)
+ if err != nil {
+ return nil, "", err
+ }
+ listener, err := sockets.NewUnixSocket(path, gid)
+ if err != nil {
+ return nil, "", err
+ }
+ return listener, path, nil
+}
+
+func fullSocketAddress(address string) (string, error) {
+ if err := os.MkdirAll(pluginSockDir, 0755); err != nil {
+ return "", err
+ }
+ if filepath.IsAbs(address) {
+ return address, nil
+ }
+ return filepath.Join(pluginSockDir, address+".sock"), nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go
new file mode 100644
index 000000000..a798b8722
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go
@@ -0,0 +1,10 @@
+// +build linux freebsd
+// +build nosystemd
+
+package sdk
+
+import "net"
+
+func setupSocketActivation() (net.Listener, error) {
+ return nil, nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go
new file mode 100644
index 000000000..5d5d8f427
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go
@@ -0,0 +1,45 @@
+// +build linux freebsd
+// +build !nosystemd
+
+package sdk
+
+import (
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/coreos/go-systemd/activation"
+)
+
+// isRunningSystemd checks whether the host was booted with systemd as its init
+// system. This functions similarly to systemd's `sd_booted(3)`: internally, it
+// checks whether /run/systemd/system/ exists and is a directory.
+// http://www.freedesktop.org/software/systemd/man/sd_booted.html
+//
+// Copied from github.com/coreos/go-systemd/util.IsRunningSystemd
+func isRunningSystemd() bool {
+ fi, err := os.Lstat("/run/systemd/system")
+ if err != nil {
+ return false
+ }
+ return fi.IsDir()
+}
+
+func setupSocketActivation() (net.Listener, error) {
+ if !isRunningSystemd() {
+ return nil, nil
+ }
+ listenFds := activation.Files(false)
+ if len(listenFds) > 1 {
+ return nil, fmt.Errorf("expected only one socket from systemd, got %d", len(listenFds))
+ }
+ var listener net.Listener
+ if len(listenFds) == 1 {
+ l, err := net.FileListener(listenFds[0])
+ if err != nil {
+ return nil, err
+ }
+ listener = l
+ }
+ return listener, nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go
new file mode 100644
index 000000000..344cf751b
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go
@@ -0,0 +1,16 @@
+// +build !linux,!freebsd
+
+package sdk
+
+import (
+ "errors"
+ "net"
+)
+
+var (
+ errOnlySupportedOnLinuxAndFreeBSD = errors.New("unix socket creation is only supported on Linux and FreeBSD")
+)
+
+func newUnixListener(pluginName string, gid int) (net.Listener, string, error) {
+ return nil, "", errOnlySupportedOnLinuxAndFreeBSD
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go
new file mode 100644
index 000000000..b5deaba6d
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go
@@ -0,0 +1,70 @@
+// +build windows
+
+package sdk
+
+import (
+ "net"
+ "os"
+ "syscall"
+ "unsafe"
+
+ "github.com/Microsoft/go-winio"
+)
+
+// Named pipes use Windows Security Descriptor Definition Language to define ACL. Following are
+// some useful definitions.
+const (
+ // This will set permissions for everyone to have full access
+ AllowEveryone = "S:(ML;;NW;;;LW)D:(A;;0x12019f;;;WD)"
+
+ // This will set permissions for Service, System, Adminstrator group and account to have full access
+ AllowServiceSystemAdmin = "D:(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;FA;;;LA)(A;ID;FA;;;LS)"
+)
+
+func newWindowsListener(address, pluginName, daemonRoot string, pipeConfig *WindowsPipeConfig) (net.Listener, string, error) {
+ winioPipeConfig := winio.PipeConfig{
+ SecurityDescriptor: pipeConfig.SecurityDescriptor,
+ InputBufferSize: pipeConfig.InBufferSize,
+ OutputBufferSize: pipeConfig.OutBufferSize,
+ }
+ listener, err := winio.ListenPipe(address, &winioPipeConfig)
+ if err != nil {
+ return nil, "", err
+ }
+
+ addr := listener.Addr().String()
+
+ specDir, err := createPluginSpecDirWindows(pluginName, addr, daemonRoot)
+ if err != nil {
+ return nil, "", err
+ }
+
+ spec, err := writeSpecFile(pluginName, addr, specDir, protoNamedPipe)
+ if err != nil {
+ return nil, "", err
+ }
+ return listener, spec, nil
+}
+
+func windowsCreateDirectoryWithACL(name string) error {
+ sa := syscall.SecurityAttributes{Length: 0}
+ sddl := "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
+ sd, err := winio.SddlToSecurityDescriptor(sddl)
+ if err != nil {
+ return &os.PathError{Op: "mkdir", Path: name, Err: err}
+ }
+ sa.Length = uint32(unsafe.Sizeof(sa))
+ sa.InheritHandle = 1
+ sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0]))
+
+ namep, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ return &os.PathError{Op: "mkdir", Path: name, Err: err}
+ }
+
+ e := syscall.CreateDirectory(namep, &sa)
+ if e != nil {
+ return &os.PathError{Op: "mkdir", Path: name, Err: e}
+ }
+ return nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go
new file mode 100644
index 000000000..0f5e113c1
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go
@@ -0,0 +1,20 @@
+// +build !windows
+
+package sdk
+
+import (
+ "errors"
+ "net"
+)
+
+var (
+ errOnlySupportedOnWindows = errors.New("named pipe creation is only supported on Windows")
+)
+
+func newWindowsListener(address, pluginName, daemonRoot string, pipeConfig *WindowsPipeConfig) (net.Listener, string, error) {
+ return nil, "", errOnlySupportedOnWindows
+}
+
+func windowsCreateDirectoryWithACL(name string) error {
+ return nil
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go
new file mode 100644
index 000000000..256fa3d67
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go
@@ -0,0 +1,13 @@
+package sdk
+
+// WindowsPipeConfig is a helper structure for configuring named pipe parameters on Windows.
+type WindowsPipeConfig struct {
+ // SecurityDescriptor contains a Windows security descriptor in SDDL format.
+ SecurityDescriptor string
+
+ // InBufferSize in bytes.
+ InBufferSize int32
+
+ // OutBufferSize in bytes.
+ OutBufferSize int32
+}
diff --git a/vendor/github.com/docker/go-plugins-helpers/volume/README.md b/vendor/github.com/docker/go-plugins-helpers/volume/README.md
new file mode 100644
index 000000000..395aa643f
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/volume/README.md
@@ -0,0 +1,36 @@
+# Docker volume extension api.
+
+Go handler to create external volume extensions for Docker.
+
+## Usage
+
+This library is designed to be integrated in your program.
+
+1. Implement the `volume.Driver` interface.
+2. Initialize a `volume.Handler` with your implementation.
+3. Call either `ServeTCP` or `ServeUnix` from the `volume.Handler`.
+
+### Example using TCP sockets:
+
+```go
+ d := MyVolumeDriver{}
+ h := volume.NewHandler(d)
+ h.ServeTCP("test_volume", ":8080")
+```
+
+### Example using Unix sockets:
+
+```go
+ d := MyVolumeDriver{}
+ h := volume.NewHandler(d)
+ u, _ := user.Lookup("root")
+ gid, _ := strconv.Atoi(u.Gid)
+ h.ServeUnix("test_volume", gid)
+```
+
+## Full example plugins
+
+- https://github.com/calavera/docker-volume-glusterfs
+- https://github.com/calavera/docker-volume-keywhiz
+- https://github.com/quobyte/docker-volume
+- https://github.com/NimbleStorage/Nemo
diff --git a/vendor/github.com/docker/go-plugins-helpers/volume/api.go b/vendor/github.com/docker/go-plugins-helpers/volume/api.go
new file mode 100644
index 000000000..dcc2f3abf
--- /dev/null
+++ b/vendor/github.com/docker/go-plugins-helpers/volume/api.go
@@ -0,0 +1,230 @@
+package volume
+
+import (
+ "log"
+ "net/http"
+
+ "github.com/docker/go-plugins-helpers/sdk"
+)
+
+const (
+ // DefaultDockerRootDirectory is the default directory where volumes will be created.
+ DefaultDockerRootDirectory = "/var/lib/docker-volumes"
+
+ manifest = `{"Implements": ["VolumeDriver"]}`
+ createPath = "/VolumeDriver.Create"
+ getPath = "/VolumeDriver.Get"
+ listPath = "/VolumeDriver.List"
+ removePath = "/VolumeDriver.Remove"
+ hostVirtualPath = "/VolumeDriver.Path"
+ mountPath = "/VolumeDriver.Mount"
+ unmountPath = "/VolumeDriver.Unmount"
+ capabilitiesPath = "/VolumeDriver.Capabilities"
+)
+
+// CreateRequest is the structure that docker's requests are deserialized to.
+type CreateRequest struct {
+ Name string
+ Options map[string]string `json:"Opts,omitempty"`
+}
+
+// RemoveRequest structure for a volume remove request
+type RemoveRequest struct {
+ Name string
+}
+
+// MountRequest structure for a volume mount request
+type MountRequest struct {
+ Name string
+ ID string
+}
+
+// MountResponse structure for a volume mount response
+type MountResponse struct {
+ Mountpoint string
+}
+
+// UnmountRequest structure for a volume unmount request
+type UnmountRequest struct {
+ Name string
+ ID string
+}
+
+// PathRequest structure for a volume path request
+type PathRequest struct {
+ Name string
+}
+
+// PathResponse structure for a volume path response
+type PathResponse struct {
+ Mountpoint string
+}
+
+// GetRequest structure for a volume get request
+type GetRequest struct {
+ Name string
+}
+
+// GetResponse structure for a volume get response
+type GetResponse struct {
+ Volume *Volume
+}
+
+// ListResponse structure for a volume list response
+type ListResponse struct {
+ Volumes []*Volume
+}
+
+// CapabilitiesResponse structure for a volume capability response
+type CapabilitiesResponse struct {
+ Capabilities Capability
+}
+
+// Volume represents a volume object for use with `Get` and `List` requests
+type Volume struct {
+ Name string
+ Mountpoint string `json:",omitempty"`
+ CreatedAt string `json:",omitempty"`
+ Status map[string]interface{} `json:",omitempty"`
+}
+
+// Capability represents the list of capabilities a volume driver can return
+type Capability struct {
+ Scope string
+}
+
+// ErrorResponse is a formatted error message that docker can understand
+type ErrorResponse struct {
+ Err string
+}
+
+// NewErrorResponse creates an ErrorResponse with the provided message
+func NewErrorResponse(msg string) *ErrorResponse {
+ return &ErrorResponse{Err: msg}
+}
+
+// Driver represent the interface a driver must fulfill.
+type Driver interface {
+ Create(*CreateRequest) error
+ List() (*ListResponse, error)
+ Get(*GetRequest) (*GetResponse, error)
+ Remove(*RemoveRequest) error
+ Path(*PathRequest) (*PathResponse, error)
+ Mount(*MountRequest) (*MountResponse, error)
+ Unmount(*UnmountRequest) error
+ Capabilities() *CapabilitiesResponse
+}
+
+// Handler forwards requests and responses between the docker daemon and the plugin.
+type Handler struct {
+ driver Driver
+ sdk.Handler
+}
+
+// NewHandler initializes the request handler with a driver implementation.
+func NewHandler(driver Driver) *Handler {
+ h := &Handler{driver, sdk.NewHandler(manifest)}
+ h.initMux()
+ return h
+}
+
+func (h *Handler) initMux() {
+ h.HandleFunc(createPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers createPath")
+ req := &CreateRequest{}
+ err := sdk.DecodeRequest(w, r, req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Create(req)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(removePath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers removePath")
+ req := &RemoveRequest{}
+ err := sdk.DecodeRequest(w, r, req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Remove(req)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(mountPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers mountPath")
+ req := &MountRequest{}
+ err := sdk.DecodeRequest(w, r, req)
+ if err != nil {
+ return
+ }
+ res, err := h.driver.Mount(req)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, res, false)
+ })
+ h.HandleFunc(hostVirtualPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers hostVirtualPath")
+ req := &PathRequest{}
+ err := sdk.DecodeRequest(w, r, req)
+ if err != nil {
+ return
+ }
+ res, err := h.driver.Path(req)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, res, false)
+ })
+ h.HandleFunc(getPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers getPath")
+ req := &GetRequest{}
+ err := sdk.DecodeRequest(w, r, req)
+ if err != nil {
+ return
+ }
+ res, err := h.driver.Get(req)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, res, false)
+ })
+ h.HandleFunc(unmountPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers unmountPath")
+ req := &UnmountRequest{}
+ err := sdk.DecodeRequest(w, r, req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Unmount(req)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(listPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers listPath")
+ res, err := h.driver.List()
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, res, false)
+ })
+
+ h.HandleFunc(capabilitiesPath, func(w http.ResponseWriter, r *http.Request) {
+ log.Println("Entering go-plugins-helpers capabilitiesPath")
+ sdk.EncodeResponse(w, h.driver.Capabilities(), false)
+ })
+}
diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go
index 2ceebc552..846636d75 100644
--- a/vendor/github.com/spf13/cobra/bash_completions.go
+++ b/vendor/github.com/spf13/cobra/bash_completions.go
@@ -5,95 +5,70 @@ import (
"fmt"
"io"
"os"
+ "sort"
+ "strings"
+
+ "github.com/spf13/pflag"
)
// Annotations for Bash completion.
const (
- BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
- // BashCompCustom should be avoided as it only works for bash.
- // Function RegisterFlagCompletionFunc() should be used instead.
+ BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
BashCompCustom = "cobra_annotation_bash_completion_custom"
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
)
-// GenBashCompletion generates bash completion file and writes to the passed writer.
-func (c *Command) GenBashCompletion(w io.Writer) error {
- return c.genBashCompletion(w, false)
-}
-
-// GenBashCompletionWithDesc generates bash completion file with descriptions and writes to the passed writer.
-func (c *Command) GenBashCompletionWithDesc(w io.Writer) error {
- return c.genBashCompletion(w, true)
-}
-
-// GenBashCompletionFile generates bash completion file.
-func (c *Command) GenBashCompletionFile(filename string) error {
- return c.genBashCompletionFile(filename, false)
-}
-
-// GenBashCompletionFileWithDesc generates bash completion file with descriptions.
-func (c *Command) GenBashCompletionFileWithDesc(filename string) error {
- return c.genBashCompletionFile(filename, true)
+func writePreamble(buf *bytes.Buffer, name string) {
+ buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
+ buf.WriteString(fmt.Sprintf(`
+__%[1]s_debug()
+{
+ if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
+ echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
+ fi
}
-func (c *Command) genBashCompletionFile(filename string, includeDesc bool) error {
- outFile, err := os.Create(filename)
- if err != nil {
- return err
- }
- defer outFile.Close()
-
- return c.genBashCompletion(outFile, includeDesc)
+# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
+# _init_completion. This is a very minimal version of that function.
+__%[1]s_init_completion()
+{
+ COMPREPLY=()
+ _get_comp_words_by_ref "$@" cur prev words cword
}
-func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error {
- buf := new(bytes.Buffer)
- if len(c.BashCompletionFunction) > 0 {
- buf.WriteString(c.BashCompletionFunction + "\n")
- }
- genBashComp(buf, c.Name(), includeDesc)
-
- _, err := buf.WriteTo(w)
- return err
+__%[1]s_index_of_word()
+{
+ local w word=$1
+ shift
+ index=0
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ index=$((index+1))
+ done
+ index=-1
}
-func genBashComp(buf *bytes.Buffer, name string, includeDesc bool) {
- compCmd := ShellCompRequestCmd
- if !includeDesc {
- compCmd = ShellCompNoDescRequestCmd
- }
-
- buf.WriteString(fmt.Sprintf(`# bash completion for %-36[1]s -*- shell-script -*-
-
-__%[1]s_debug()
+__%[1]s_contains_word()
{
- if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
- echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
- fi
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+ return 1
}
-__%[1]s_perform_completion()
+__%[1]s_handle_go_custom_completion()
{
- __%[1]s_debug
- __%[1]s_debug "========= starting completion logic =========="
- __%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
-
- # The user could have moved the cursor backwards on the command-line.
- # We need to trigger completion from the $cword location, so we need
- # to truncate the command-line ($words) up to the $cword location.
- words=("${words[@]:0:$cword+1}")
- __%[1]s_debug "Truncated words[*]: ${words[*]},"
+ __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
local shellCompDirectiveError=%[3]d
local shellCompDirectiveNoSpace=%[4]d
local shellCompDirectiveNoFileComp=%[5]d
local shellCompDirectiveFilterFileExt=%[6]d
local shellCompDirectiveFilterDirs=%[7]d
- local shellCompDirectiveLegacyCustomComp=%[8]d
- local shellCompDirectiveLegacyCustomArgsComp=%[9]d
- local out requestComp lastParam lastChar comp directive args flagPrefix
+ local out requestComp lastParam lastChar comp directive args
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly %[1]s allows to handle aliases
@@ -102,24 +77,16 @@ __%[1]s_perform_completion()
lastParam=${words[$((${#words[@]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
- __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"
+ __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
- __%[1]s_debug "Adding extra empty parameter"
+ __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter"
requestComp="${requestComp} \"\""
fi
- # When completing a flag with an = (e.g., %[1]s -n=<TAB>)
- # bash focuses on the part after the =, so we need to remove
- # the flag part from $cur
- if [[ "${cur}" == -*=* ]]; then
- flagPrefix="${cur%%%%=*}="
- cur="${cur#*=}"
- fi
-
- __%[1]s_debug "Calling ${requestComp}"
+ __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}"
# Use eval to handle any environment variables and such
out=$(eval "${requestComp}" 2>/dev/null)
@@ -131,23 +98,23 @@ __%[1]s_perform_completion()
# There is not directive specified
directive=0
fi
- __%[1]s_debug "The completion directive is: ${directive}"
- __%[1]s_debug "The completions are: ${out[*]}"
+ __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
+ __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
# Error code. No completion.
- __%[1]s_debug "Received error from custom completion go code"
+ __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
return
else
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
- __%[1]s_debug "Activating no space"
+ __%[1]s_debug "${FUNCNAME[0]}: activating no space"
compopt -o nospace
fi
fi
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
- __%[1]s_debug "Activating no file completion"
+ __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
compopt +o default
fi
fi
@@ -156,7 +123,6 @@ __%[1]s_perform_completion()
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
# File extension filtering
local fullFilter filter filteringCmd
-
# Do not use quotes around the $out variable or else newline
# characters will be kept.
for filter in ${out[*]}; do
@@ -168,173 +134,545 @@ __%[1]s_perform_completion()
$filteringCmd
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
# File completion for directories only
-
+ local subDir
# Use printf to strip any trailing newline
- local subdir
subdir=$(printf "%%s" "${out[0]}")
if [ -n "$subdir" ]; then
__%[1]s_debug "Listing directories in $subdir"
- pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
+ __%[1]s_handle_subdirs_in_dir_flag "$subdir"
else
__%[1]s_debug "Listing directories in ."
_filedir -d
fi
- elif [ $((directive & shellCompDirectiveLegacyCustomComp)) -ne 0 ]; then
- local cmd
- __%[1]s_debug "Legacy custom completion. Directive: $directive, cmds: ${out[*]}"
-
- # The following variables should get their value through the commands
- # we have received as completions and are parsing below.
- local last_command
- local nouns
-
- # Execute every command received
- while IFS='' read -r cmd; do
- __%[1]s_debug "About to execute: $cmd"
- eval "$cmd"
- done < <(printf "%%s\n" "${out[@]}")
-
- __%[1]s_debug "last_command: $last_command"
- __%[1]s_debug "nouns[0]: ${nouns[0]}, nouns[1]: ${nouns[1]}"
-
- if [ $((directive & shellCompDirectiveLegacyCustomArgsComp)) -ne 0 ]; then
- # We should call the global legacy custom completion function, if it is defined
- if declare -F __%[1]s_custom_func >/dev/null; then
- # Use command name qualified legacy custom func
- __%[1]s_debug "About to call: __%[1]s_custom_func"
- __%[1]s_custom_func
- elif declare -F __custom_func >/dev/null; then
- # Otherwise fall back to unqualified legacy custom func for compatibility
- __%[1]s_debug "About to call: __custom_func"
- __custom_func
- fi
- fi
else
- local tab
- tab=$(printf '\t')
- local longest=0
- # Look for the longest completion so that we can format things nicely
while IFS='' read -r comp; do
- comp=${comp%%%%$tab*}
- if ((${#comp}>longest)); then
- longest=${#comp}
+ COMPREPLY+=("$comp")
+ done < <(compgen -W "${out[*]}" -- "$cur")
+ fi
+}
+
+__%[1]s_handle_reply()
+{
+ __%[1]s_debug "${FUNCNAME[0]}"
+ local comp
+ case $cur in
+ -*)
+ if [[ $(type -t compopt) = "builtin" ]]; then
+ compopt -o nospace
+ fi
+ local allflags
+ if [ ${#must_have_one_flag[@]} -ne 0 ]; then
+ allflags=("${must_have_one_flag[@]}")
+ else
+ allflags=("${flags[*]} ${two_word_flags[*]}")
+ fi
+ while IFS='' read -r comp; do
+ COMPREPLY+=("$comp")
+ done < <(compgen -W "${allflags[*]}" -- "$cur")
+ if [[ $(type -t compopt) = "builtin" ]]; then
+ [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
fi
- done < <(printf "%%s\n" "${out[@]}")
- local completions=()
- while IFS='' read -r comp; do
- if [ -z "$comp" ]; then
- continue
+ # complete after --flag=abc
+ if [[ $cur == *=* ]]; then
+ if [[ $(type -t compopt) = "builtin" ]]; then
+ compopt +o nospace
+ fi
+
+ local index flag
+ flag="${cur%%=*}"
+ __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
+ COMPREPLY=()
+ if [[ ${index} -ge 0 ]]; then
+ PREFIX=""
+ cur="${cur#*=}"
+ ${flags_completion[${index}]}
+ if [ -n "${ZSH_VERSION}" ]; then
+ # zsh completion needs --flag= prefix
+ eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
+ fi
+ fi
fi
+ return 0;
+ ;;
+ esac
+
+ # check if we are handling a flag with special work handling
+ local index
+ __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
+ if [[ ${index} -ge 0 ]]; then
+ ${flags_completion[${index}]}
+ return
+ fi
- __%[1]s_debug "Original comp: $comp"
- comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")"
- __%[1]s_debug "Final comp: $comp"
- completions+=("$comp")
- done < <(printf "%%s\n" "${out[@]}")
+ # we are parsing a flag and don't have a special handler, no completion
+ if [[ ${cur} != "${words[cword]}" ]]; then
+ return
+ fi
+ local completions
+ completions=("${commands[@]}")
+ if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
+ completions+=("${must_have_one_noun[@]}")
+ elif [[ -n "${has_completion_function}" ]]; then
+ # if a go completion function is provided, defer to that function
+ __%[1]s_handle_go_custom_completion
+ fi
+ if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
+ completions+=("${must_have_one_flag[@]}")
+ fi
+ while IFS='' read -r comp; do
+ COMPREPLY+=("$comp")
+ done < <(compgen -W "${completions[*]}" -- "$cur")
+
+ if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
while IFS='' read -r comp; do
- # Although this script should only be used for bash
- # there may be programs that still convert the bash
- # script into a zsh one. To continue supporting those
- # programs, we do this single adaptation for zsh
- if [ -n "${ZSH_VERSION}" ]; then
- # zsh completion needs --flag= prefix
- COMPREPLY+=("$flagPrefix$comp")
- else
- COMPREPLY+=("$comp")
- fi
- done < <(compgen -W "${completions[*]}" -- "$cur")
-
- # If there is a single completion left, remove the description text
- if [ ${#COMPREPLY[*]} -eq 1 ]; then
- __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
- comp="${COMPREPLY[0]%%%% *}"
- __%[1]s_debug "Removed description from single completion, which is now: ${comp}"
- COMPREPLY=()
COMPREPLY+=("$comp")
+ done < <(compgen -W "${noun_aliases[*]}" -- "$cur")
+ fi
+
+ if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
+ if declare -F __%[1]s_custom_func >/dev/null; then
+ # try command name qualified custom func
+ __%[1]s_custom_func
+ else
+ # otherwise fall back to unqualified for compatibility
+ declare -F __custom_func >/dev/null && __custom_func
+ fi
+ fi
+
+ # available in bash-completion >= 2, not always present on macOS
+ if declare -F __ltrim_colon_completions >/dev/null; then
+ __ltrim_colon_completions "$cur"
+ fi
+
+ # If there is only 1 completion and it is a flag with an = it will be completed
+ # but we don't want a space after the =
+ if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
+ compopt -o nospace
+ fi
+}
+
+# The arguments should be in the form "ext1|ext2|extn"
+__%[1]s_handle_filename_extension_flag()
+{
+ local ext="$1"
+ _filedir "@(${ext})"
+}
+
+__%[1]s_handle_subdirs_in_dir_flag()
+{
+ local dir="$1"
+ pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
+}
+
+__%[1]s_handle_flag()
+{
+ __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
+
+ # if a command required a flag, and we found it, unset must_have_one_flag()
+ local flagname=${words[c]}
+ local flagvalue
+ # if the word contained an =
+ if [[ ${words[c]} == *"="* ]]; then
+ flagvalue=${flagname#*=} # take in as flagvalue after the =
+ flagname=${flagname%%=*} # strip everything after the =
+ flagname="${flagname}=" # but put the = back
+ fi
+ __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
+ if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
+ must_have_one_flag=()
+ fi
+
+ # if you set a flag which only applies to this command, don't show subcommands
+ if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
+ commands=()
+ fi
+
+ # keep flag value with flagname as flaghash
+ # flaghash variable is an associative array which is only supported in bash > 3.
+ if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
+ if [ -n "${flagvalue}" ] ; then
+ flaghash[${flagname}]=${flagvalue}
+ elif [ -n "${words[ $((c+1)) ]}" ] ; then
+ flaghash[${flagname}]=${words[ $((c+1)) ]}
+ else
+ flaghash[${flagname}]="true" # pad "true" for bool flag
+ fi
+ fi
+
+ # skip the argument to a two word flag
+ if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
+ __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument"
+ c=$((c+1))
+ # if we are looking for a flags value, don't show commands
+ if [[ $c -eq $cword ]]; then
+ commands=()
fi
fi
- __%[1]s_handle_special_char "$cur" :
- __%[1]s_handle_special_char "$cur" =
+ c=$((c+1))
+
}
-__%[1]s_handle_special_char()
+__%[1]s_handle_noun()
{
- local comp="$1"
- local char=$2
- if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
- local word=${comp%%"${comp##*${char}}"}
- local idx=${#COMPREPLY[*]}
- while [[ $((--idx)) -ge 0 ]]; do
- COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
- done
+ __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
+
+ if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
+ must_have_one_noun=()
+ elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
+ must_have_one_noun=()
fi
+
+ nouns+=("${words[c]}")
+ c=$((c+1))
}
-__%[1]s_format_comp_descriptions()
+__%[1]s_handle_command()
{
- local tab
- tab=$(printf '\t')
- local comp="$1"
- local longest=$2
-
- # Properly format the description string which follows a tab character if there is one
- if [[ "$comp" == *$tab* ]]; then
- desc=${comp#*$tab}
- comp=${comp%%%%$tab*}
-
- # $COLUMNS stores the current shell width.
- # Remove an extra 4 because we add 2 spaces and 2 parentheses.
- maxdesclength=$(( COLUMNS - longest - 4 ))
-
- # Make sure we can fit a description of at least 8 characters
- # if we are to align the descriptions.
- if [[ $maxdesclength -gt 8 ]]; then
- # Add the proper number of spaces to align the descriptions
- for ((i = ${#comp} ; i < longest ; i++)); do
- comp+=" "
- done
+ __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
+
+ local next_command
+ if [[ -n ${last_command} ]]; then
+ next_command="_${last_command}_${words[c]//:/__}"
+ else
+ if [[ $c -eq 0 ]]; then
+ next_command="_%[1]s_root_command"
else
- # Don't pad the descriptions so we can fit more text after the completion
- maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
+ next_command="_${words[c]//:/__}"
fi
+ fi
+ c=$((c+1))
+ __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
+ declare -F "$next_command" >/dev/null && $next_command
+}
- # If there is enough space for any description text,
- # truncate the descriptions that are too long for the shell width
- if [ $maxdesclength -gt 0 ]; then
- if [ ${#desc} -gt $maxdesclength ]; then
- desc=${desc:0:$(( maxdesclength - 1 ))}
- desc+="…"
- fi
- comp+=" ($desc)"
+__%[1]s_handle_word()
+{
+ if [[ $c -ge $cword ]]; then
+ __%[1]s_handle_reply
+ return
+ fi
+ __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
+ if [[ "${words[c]}" == -* ]]; then
+ __%[1]s_handle_flag
+ elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
+ __%[1]s_handle_command
+ elif [[ $c -eq 0 ]]; then
+ __%[1]s_handle_command
+ elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then
+ # aliashash variable is an associative array which is only supported in bash > 3.
+ if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
+ words[c]=${aliashash[${words[c]}]}
+ __%[1]s_handle_command
+ else
+ __%[1]s_handle_noun
fi
+ else
+ __%[1]s_handle_noun
fi
+ __%[1]s_handle_word
+}
- # Must use printf to escape all special characters
- printf "%%q" "${comp}"
+`, name, ShellCompNoDescRequestCmd,
+ ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
+ ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
}
-__start_%[1]s()
-{
+func writePostscript(buf *bytes.Buffer, name string) {
+ name = strings.Replace(name, ":", "__", -1)
+ buf.WriteString(fmt.Sprintf("__start_%s()\n", name))
+ buf.WriteString(fmt.Sprintf(`{
local cur prev words cword
+ declare -A flaghash 2>/dev/null || :
+ declare -A aliashash 2>/dev/null || :
+ if declare -F _init_completion >/dev/null 2>&1; then
+ _init_completion -s || return
+ else
+ __%[1]s_init_completion -n "=" || return
+ fi
- COMPREPLY=()
- _get_comp_words_by_ref -n "=:" cur prev words cword
-
- __%[1]s_perform_completion
+ local c=0
+ local flags=()
+ local two_word_flags=()
+ local local_nonpersistent_flags=()
+ local flags_with_completion=()
+ local flags_completion=()
+ local commands=("%[1]s")
+ local must_have_one_flag=()
+ local must_have_one_noun=()
+ local has_completion_function
+ local last_command
+ local nouns=()
+
+ __%[1]s_handle_word
}
-if [[ $(type -t compopt) = "builtin" ]]; then
- complete -o default -F __start_%[1]s %[1]s
+`, name))
+ buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
+ complete -o default -F __start_%s %s
else
- complete -o default -o nospace -F __start_%[1]s %[1]s
+ complete -o default -o nospace -F __start_%s %s
fi
-# ex: ts=4 sw=4 et filetype=sh
-`, name, compCmd,
- ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
- ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
- shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp))
+`, name, name, name, name))
+ buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n")
+}
+
+func writeCommands(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" commands=()\n")
+ for _, c := range cmd.Commands() {
+ if !c.IsAvailableCommand() && c != cmd.helpCommand {
+ continue
+ }
+ buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
+ writeCmdAliases(buf, c)
+ }
+ buf.WriteString("\n")
+}
+
+func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
+ for key, value := range annotations {
+ switch key {
+ case BashCompFilenameExt:
+ buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
+
+ var ext string
+ if len(value) > 0 {
+ ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
+ } else {
+ ext = "_filedir"
+ }
+ buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
+ case BashCompCustom:
+ buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
+ if len(value) > 0 {
+ handlers := strings.Join(value, "; ")
+ buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers))
+ } else {
+ buf.WriteString(" flags_completion+=(:)\n")
+ }
+ case BashCompSubdirsInDir:
+ buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
+
+ var ext string
+ if len(value) == 1 {
+ ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
+ } else {
+ ext = "_filedir -d"
+ }
+ buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
+ }
+ }
+}
+
+func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
+ name := flag.Shorthand
+ format := " "
+ if len(flag.NoOptDefVal) == 0 {
+ format += "two_word_"
+ }
+ format += "flags+=(\"-%s\")\n"
+ buf.WriteString(fmt.Sprintf(format, name))
+ writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
+}
+
+func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
+ name := flag.Name
+ format := " flags+=(\"--%s"
+ if len(flag.NoOptDefVal) == 0 {
+ format += "="
+ }
+ format += "\")\n"
+ buf.WriteString(fmt.Sprintf(format, name))
+ if len(flag.NoOptDefVal) == 0 {
+ format = " two_word_flags+=(\"--%s\")\n"
+ buf.WriteString(fmt.Sprintf(format, name))
+ }
+ writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
+}
+
+func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
+ name := flag.Name
+ format := " local_nonpersistent_flags+=(\"--%[1]s\")\n"
+ if len(flag.NoOptDefVal) == 0 {
+ format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n"
+ }
+ buf.WriteString(fmt.Sprintf(format, name))
+ if len(flag.Shorthand) > 0 {
+ buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
+ }
+}
+
+// Setup annotations for go completions for registered flags
+func prepareCustomAnnotationsForFlags(cmd *Command) {
+ for flag := range flagCompletionFunctions {
+ // Make sure the completion script calls the __*_go_custom_completion function for
+ // every registered flag. We need to do this here (and not when the flag was registered
+ // for completion) so that we can know the root command name for the prefix
+ // of __<prefix>_go_custom_completion
+ if flag.Annotations == nil {
+ flag.Annotations = map[string][]string{}
+ }
+ flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
+ }
+}
+
+func writeFlags(buf *bytes.Buffer, cmd *Command) {
+ prepareCustomAnnotationsForFlags(cmd)
+ buf.WriteString(` flags=()
+ two_word_flags=()
+ local_nonpersistent_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+`)
+ localNonPersistentFlags := cmd.LocalNonPersistentFlags()
+ cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ if nonCompletableFlag(flag) {
+ return
+ }
+ writeFlag(buf, flag, cmd)
+ if len(flag.Shorthand) > 0 {
+ writeShortFlag(buf, flag, cmd)
+ }
+ // localNonPersistentFlags are used to stop the completion of subcommands when one is set
+ // if TraverseChildren is true we should allow to complete subcommands
+ if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
+ writeLocalNonPersistentFlag(buf, flag)
+ }
+ })
+ cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ if nonCompletableFlag(flag) {
+ return
+ }
+ writeFlag(buf, flag, cmd)
+ if len(flag.Shorthand) > 0 {
+ writeShortFlag(buf, flag, cmd)
+ }
+ })
+
+ buf.WriteString("\n")
+}
+
+func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" must_have_one_flag=()\n")
+ flags := cmd.NonInheritedFlags()
+ flags.VisitAll(func(flag *pflag.Flag) {
+ if nonCompletableFlag(flag) {
+ return
+ }
+ for key := range flag.Annotations {
+ switch key {
+ case BashCompOneRequiredFlag:
+ format := " must_have_one_flag+=(\"--%s"
+ if flag.Value.Type() != "bool" {
+ format += "="
+ }
+ format += "\")\n"
+ buf.WriteString(fmt.Sprintf(format, flag.Name))
+
+ if len(flag.Shorthand) > 0 {
+ buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand))
+ }
+ }
+ }
+ })
+}
+
+func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" must_have_one_noun=()\n")
+ sort.Sort(sort.StringSlice(cmd.ValidArgs))
+ for _, value := range cmd.ValidArgs {
+ // Remove any description that may be included following a tab character.
+ // Descriptions are not supported by bash completion.
+ value = strings.Split(value, "\t")[0]
+ buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
+ }
+ if cmd.ValidArgsFunction != nil {
+ buf.WriteString(" has_completion_function=1\n")
+ }
+}
+
+func writeCmdAliases(buf *bytes.Buffer, cmd *Command) {
+ if len(cmd.Aliases) == 0 {
+ return
+ }
+
+ sort.Sort(sort.StringSlice(cmd.Aliases))
+
+ buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n"))
+ for _, value := range cmd.Aliases {
+ buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value))
+ buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name()))
+ }
+ buf.WriteString(` fi`)
+ buf.WriteString("\n")
+}
+func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
+ buf.WriteString(" noun_aliases=()\n")
+ sort.Sort(sort.StringSlice(cmd.ArgAliases))
+ for _, value := range cmd.ArgAliases {
+ buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value))
+ }
+}
+
+func gen(buf *bytes.Buffer, cmd *Command) {
+ for _, c := range cmd.Commands() {
+ if !c.IsAvailableCommand() && c != cmd.helpCommand {
+ continue
+ }
+ gen(buf, c)
+ }
+ commandName := cmd.CommandPath()
+ commandName = strings.Replace(commandName, " ", "_", -1)
+ commandName = strings.Replace(commandName, ":", "__", -1)
+
+ if cmd.Root() == cmd {
+ buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
+ } else {
+ buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
+ }
+
+ buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
+ buf.WriteString("\n")
+ buf.WriteString(" command_aliases=()\n")
+ buf.WriteString("\n")
+
+ writeCommands(buf, cmd)
+ writeFlags(buf, cmd)
+ writeRequiredFlag(buf, cmd)
+ writeRequiredNouns(buf, cmd)
+ writeArgAliases(buf, cmd)
+ buf.WriteString("}\n\n")
+}
+
+// GenBashCompletion generates bash completion file and writes to the passed writer.
+func (c *Command) GenBashCompletion(w io.Writer) error {
+ buf := new(bytes.Buffer)
+ writePreamble(buf, c.Name())
+ if len(c.BashCompletionFunction) > 0 {
+ buf.WriteString(c.BashCompletionFunction + "\n")
+ }
+ gen(buf, c)
+ writePostscript(buf, c.Name())
+
+ _, err := buf.WriteTo(w)
+ return err
+}
+
+func nonCompletableFlag(flag *pflag.Flag) bool {
+ return flag.Hidden || len(flag.Deprecated) > 0
+}
+
+// GenBashCompletionFile generates bash completion file.
+func (c *Command) GenBashCompletionFile(filename string) error {
+ outFile, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer outFile.Close()
+
+ return c.GenBashCompletion(outFile)
}
diff --git a/vendor/github.com/spf13/cobra/custom_completions.go b/vendor/github.com/spf13/cobra/custom_completions.go
index e2c68ae1e..f9e88e081 100644
--- a/vendor/github.com/spf13/cobra/custom_completions.go
+++ b/vendor/github.com/spf13/cobra/custom_completions.go
@@ -51,11 +51,6 @@ const (
// obtain the same behavior but only for flags.
ShellCompDirectiveFilterDirs
- // For internal use only.
- // Used to maintain backwards-compatibility with the legacy bash custom completions.
- shellCompDirectiveLegacyCustomComp
- shellCompDirectiveLegacyCustomArgsComp
-
// ===========================================================================
// All directives using iota should be above this one.
@@ -99,12 +94,6 @@ func (d ShellCompDirective) string() string {
if d&ShellCompDirectiveFilterDirs != 0 {
directives = append(directives, "ShellCompDirectiveFilterDirs")
}
- if d&shellCompDirectiveLegacyCustomComp != 0 {
- directives = append(directives, "shellCompDirectiveLegacyCustomComp")
- }
- if d&shellCompDirectiveLegacyCustomArgsComp != 0 {
- directives = append(directives, "shellCompDirectiveLegacyCustomArgsComp")
- }
if len(directives) == 0 {
directives = append(directives, "ShellCompDirectiveDefault")
}
@@ -160,6 +149,10 @@ func (c *Command) initCompleteCmd(args []string) {
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
}
+ if directive >= shellCompDirectiveMaxValue {
+ directive = ShellCompDirectiveDefault
+ }
+
// As the last printout, print the completion directive for the completion script to parse.
// The directive integer must be that last character following a single colon (:).
// The completion script expects :<directive>
@@ -369,10 +362,6 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
var comps []string
comps, directive = completionFn(finalCmd, finalArgs, toComplete)
completions = append(completions, comps...)
- } else {
- // If there is no Go custom completion defined, check for legacy bash
- // custom completion to preserve backwards-compatibility
- completions, directive = checkLegacyCustomCompletion(finalCmd, finalArgs, flag, completions, directive)
}
return finalCmd, completions, directive, nil
@@ -453,16 +442,7 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
if len(lastArg) > 0 && lastArg[0] == '-' {
if index := strings.Index(lastArg, "="); index >= 0 {
// Flag with an =
- if strings.HasPrefix(lastArg[:index], "--") {
- // Flag has full name
- flagName = lastArg[2:index]
- } else {
- // Flag is shorthand
- // We have to get the last shorthand flag name
- // e.g. `-asd` => d to provide the correct completion
- // https://github.com/spf13/cobra/issues/1257
- flagName = lastArg[index-1 : index]
- }
+ flagName = strings.TrimLeft(lastArg[:index], "-")
lastArg = lastArg[index+1:]
flagWithEqual = true
} else {
@@ -479,16 +459,8 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
// If the flag contains an = it means it has already been fully processed,
// so we don't need to deal with it here.
if index := strings.Index(prevArg, "="); index < 0 {
- if strings.HasPrefix(prevArg, "--") {
- // Flag has full name
- flagName = prevArg[2:]
- } else {
- // Flag is shorthand
- // We have to get the last shorthand flag name
- // e.g. `-asd` => d to provide the correct completion
- // https://github.com/spf13/cobra/issues/1257
- flagName = prevArg[len(prevArg)-1:]
- }
+ flagName = strings.TrimLeft(prevArg, "-")
+
// Remove the uncompleted flag or else there could be an error created
// for an invalid value for that flag
trimmedArgs = args[:len(args)-1]
@@ -541,65 +513,6 @@ func findFlag(cmd *Command, name string) *pflag.Flag {
return cmd.Flag(name)
}
-func nonCompletableFlag(flag *pflag.Flag) bool {
- return flag.Hidden || len(flag.Deprecated) > 0
-}
-
-// This function checks if legacy bash custom completion should be performed and if so,
-// it provides the shell script with the necessary information.
-func checkLegacyCustomCompletion(cmd *Command, args []string, flag *pflag.Flag, completions []string, directive ShellCompDirective) ([]string, ShellCompDirective) {
- // Check if any legacy custom completion is defined for the program
- if len(cmd.Root().BashCompletionFunction) > 0 {
- // Legacy custom completion is only triggered if no other completions were found.
- if len(completions) == 0 {
- if flag != nil {
- // For legacy custom flag completion, we must let the script know the bash
- // functions it should call based on the content of the annotation BashCompCustom.
- if values, present := flag.Annotations[BashCompCustom]; present {
- if len(values) > 0 {
- handlers := strings.Join(values, "; ")
- // We send the commands to set the shell variables that are needed
- // for legacy custom completions followed by the functions to call
- // to perform the actual flag completion
- completions = append(prepareLegacyCustomCompletionVars(cmd, args), handlers)
- directive = directive | shellCompDirectiveLegacyCustomComp
- }
- }
- } else {
- // Check if the legacy custom_func is defined.
- // This check will work for both "__custom_func" and "__<program>_custom_func".
- // This could happen if the program defined some functions for legacy flag completion
- // but not the legacy custom_func.
- if strings.Contains(cmd.Root().BashCompletionFunction, "_custom_func") {
- // For legacy args completion, the script already knows what to call
- // so we only need to tell it the commands to set the shell variables needed
- completions = prepareLegacyCustomCompletionVars(cmd, args)
- directive = directive | shellCompDirectiveLegacyCustomComp | shellCompDirectiveLegacyCustomArgsComp
- }
- }
- }
- }
- return completions, directive
-}
-
-// The original bash completion script had some shell variables that are used by legacy bash
-// custom completions. Let's set those variables to allow those legacy custom completions
-// to continue working.
-func prepareLegacyCustomCompletionVars(cmd *Command, args []string) []string {
- var compVarCmds []string
-
- // "last_command" variable
- commandName := cmd.CommandPath()
- commandName = strings.Replace(commandName, " ", "_", -1)
- commandName = strings.Replace(commandName, ":", "__", -1)
- compVarCmds = append(compVarCmds, fmt.Sprintf("last_command=%s", commandName))
-
- // "nouns" array variable
- compVarCmds = append(compVarCmds, fmt.Sprintf("nouns=(%s)", strings.Join(args, " ")))
-
- return compVarCmds
-}
-
// CompDebug prints the specified string to the same file as where the
// completion script prints its logs.
// Note that completion printouts should never be on stdout as they would
diff --git a/vendor/github.com/spf13/cobra/fish_completions.go b/vendor/github.com/spf13/cobra/fish_completions.go
index aeaef0087..eaae9bca8 100644
--- a/vendor/github.com/spf13/cobra/fish_completions.go
+++ b/vendor/github.com/spf13/cobra/fish_completions.go
@@ -28,9 +28,9 @@ function __%[1]s_debug
end
function __%[1]s_perform_completion
- __%[1]s_debug "Starting __%[1]s_perform_completion"
+ __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv"
- set args (string split -- " " (commandline -c))
+ set args (string split -- " " "$argv")
set lastArg "$args[-1]"
__%[1]s_debug "args: $args"
@@ -71,22 +71,31 @@ function __%[1]s_perform_completion
printf "%%s\n" "$directiveLine"
end
-# This function does two things:
-# - Obtain the completions and store them in the global __%[1]s_comp_results
-# - Return false if file completion should be performed
+# This function does three things:
+# 1- Obtain the completions and store them in the global __%[1]s_comp_results
+# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed
+# and unset it otherwise
+# 3- Return true if the completion results are not empty
function __%[1]s_prepare_completions
- __%[1]s_debug ""
- __%[1]s_debug "========= starting completion logic =========="
-
# Start fresh
+ set --erase __%[1]s_comp_do_file_comp
set --erase __%[1]s_comp_results
- set results (__%[1]s_perform_completion)
+ # Check if the command-line is already provided. This is useful for testing.
+ if not set --query __%[1]s_comp_commandLine
+ # Use the -c flag to allow for completion in the middle of the line
+ set __%[1]s_comp_commandLine (commandline -c)
+ end
+ __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
+
+ set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine")
+ set --erase __%[1]s_comp_commandLine
__%[1]s_debug "Completion results: $results"
if test -z "$results"
__%[1]s_debug "No completion, probably due to a failure"
# Might as well do file completion, in case it helps
+ set --global __%[1]s_comp_do_file_comp 1
return 1
end
@@ -101,8 +110,6 @@ function __%[1]s_prepare_completions
set shellCompDirectiveNoFileComp %[6]d
set shellCompDirectiveFilterFileExt %[7]d
set shellCompDirectiveFilterDirs %[8]d
- set shellCompDirectiveLegacyCustomComp %[9]d
- set shellCompDirectiveLegacyCustomArgsComp %[10]d
if test -z "$directive"
set directive 0
@@ -112,14 +119,6 @@ function __%[1]s_prepare_completions
if test $compErr -eq 1
__%[1]s_debug "Received error directive: aborting."
# Might as well do file completion, in case it helps
- return 1
- end
-
- set legacyCustom (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomComp) %% 2)
- set legacyCustomArgs (math (math --scale 0 $directive / $shellCompDirectiveLegacyCustomArgsComp) %% 2)
- if test $legacyCustom -eq 1; or test $legacyCustomArgs -eq 1
- __%[1]s_debug "Legacy bash custom completion not applicable to fish"
- # Do full file completion instead
set --global __%[1]s_comp_do_file_comp 1
return 1
end
@@ -129,6 +128,7 @@ function __%[1]s_prepare_completions
if test $filefilter -eq 1; or test $dirfilter -eq 1
__%[1]s_debug "File extension filtering or directory filtering not supported"
# Do full file completion instead
+ set --global __%[1]s_comp_do_file_comp 1
return 1
end
@@ -137,51 +137,27 @@ function __%[1]s_prepare_completions
__%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
- # If we want to prevent a space, or if file completion is NOT disabled,
- # we need to count the number of valid completions.
- # To do so, we will filter on prefix as the completions we have received
- # may not already be filtered so as to allow fish to match on different
- # criteria than prefix.
- if test $nospace -ne 0; or test $nofiles -eq 0
- set prefix (commandline -t)
- __%[1]s_debug "prefix: $prefix"
-
- set completions
- for comp in $__%[1]s_comp_results
- if test (string match -e -r "^$prefix" "$comp")
- set -a completions $comp
- end
- end
- set --global __%[1]s_comp_results $completions
- __%[1]s_debug "Filtered completions are: $__%[1]s_comp_results"
-
- # Important not to quote the variable for count to work
- set numComps (count $__%[1]s_comp_results)
- __%[1]s_debug "numComps: $numComps"
-
- if test $numComps -eq 1; and test $nospace -ne 0
- # To support the "nospace" directive we trick the shell
- # by outputting an extra, longer completion.
- # We must first split on \t to get rid of the descriptions because
- # the extra character we add to the fake second completion must be
- # before the description. We don't need descriptions anyway since
- # there is only a single real completion which the shell will expand
- # immediately.
- __%[1]s_debug "Adding second completion to perform nospace directive"
- set split (string split --max 1 \t $__%[1]s_comp_results[1])
- set --global __%[1]s_comp_results $split[1] $split[1].
- __%[1]s_debug "Completions are now: $__%[1]s_comp_results"
- end
-
- if test $numComps -eq 0; and test $nofiles -eq 0
- # To be consistent with bash and zsh, we only trigger file
- # completion when there are no other completions
- __%[1]s_debug "Requesting file completion"
- return 1
- end
+ # Important not to quote the variable for count to work
+ set numComps (count $__%[1]s_comp_results)
+ __%[1]s_debug "numComps: $numComps"
+
+ if test $numComps -eq 1; and test $nospace -ne 0
+ # To support the "nospace" directive we trick the shell
+ # by outputting an extra, longer completion.
+ __%[1]s_debug "Adding second completion to perform nospace directive"
+ set --append __%[1]s_comp_results $__%[1]s_comp_results[1].
end
- return 0
+ if test $numComps -eq 0; and test $nofiles -eq 0
+ __%[1]s_debug "Requesting file completion"
+ set --global __%[1]s_comp_do_file_comp 1
+ end
+
+ # If we don't want file completion, we must return true even if there
+ # are no completions found. This is because fish will perform the last
+ # completion command, even if its condition is false, if no other
+ # completion command was triggered
+ return (not set --query __%[1]s_comp_do_file_comp)
end
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
@@ -194,14 +170,21 @@ complete --do-complete "%[2]s " > /dev/null 2>&1
# Remove any pre-existing completions for the program since we will be handling all of them.
complete -c %[2]s -e
-# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
-# which provides the program's completion choices.
+# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
+# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
+#
+# This completion will be run second as complete commands are added FILO.
+# It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
+complete -c %[2]s -n 'set --query __%[1]s_comp_do_file_comp'
+
+# This completion will be run first as complete commands are added FILO.
+# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results and __%[1]s_comp_do_file_comp.
+# It provides the program's completion choices.
complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
`, nameForVar, name, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
- ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
- shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp))
+ ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
}
// GenFishCompletion generates fish completion file and writes to the passed writer.
diff --git a/vendor/github.com/spf13/cobra/zsh_completions.go b/vendor/github.com/spf13/cobra/zsh_completions.go
index ef290e2eb..92a70394a 100644
--- a/vendor/github.com/spf13/cobra/zsh_completions.go
+++ b/vendor/github.com/spf13/cobra/zsh_completions.go
@@ -94,10 +94,8 @@ _%[1]s()
local shellCompDirectiveNoFileComp=%[5]d
local shellCompDirectiveFilterFileExt=%[6]d
local shellCompDirectiveFilterDirs=%[7]d
- local shellCompDirectiveLegacyCustomComp=%[8]d
- local shellCompDirectiveLegacyCustomArgsComp=%[9]d
- local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace
+ local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp
local -a completions
__%[1]s_debug "\n========= starting completion logic =========="
@@ -165,6 +163,7 @@ _%[1]s()
return
fi
+ compCount=0
while IFS='\n' read -r comp; do
if [ -n "$comp" ]; then
# If requested, completions are returned with a description.
@@ -176,17 +175,13 @@ _%[1]s()
local tab=$(printf '\t')
comp=${comp//$tab/:}
+ ((compCount++))
__%[1]s_debug "Adding completion: ${comp}"
completions+=${comp}
lastComp=$comp
fi
done < <(printf "%%s\n" "${out[@]}")
- if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
- __%[1]s_debug "Activating nospace."
- noSpace="-S ''"
- fi
-
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
# File extension filtering
local filteringCmd
@@ -213,40 +208,25 @@ _%[1]s()
__%[1]s_debug "Listing directories in ."
fi
- local result
_arguments '*:dirname:_files -/'" ${flagPrefix}"
- result=$?
if [ -n "$subdir" ]; then
popd >/dev/null 2>&1
fi
- return $result
- else
- __%[1]s_debug "Calling _describe"
- if eval _describe "completions" completions $flagPrefix $noSpace; then
- __%[1]s_debug "_describe found some completions"
-
- # Return the success of having called _describe
- return 0
+ elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then
+ __%[1]s_debug "Activating nospace."
+ # We can use compadd here as there is no description when
+ # there is only one completion.
+ compadd -S '' "${lastComp}"
+ elif [ ${compCount} -eq 0 ]; then
+ if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
+ __%[1]s_debug "deactivating file completion"
else
- __%[1]s_debug "_describe did not find completions."
- __%[1]s_debug "Checking if we should do file completion."
- if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
- __%[1]s_debug "deactivating file completion"
-
- # We must return an error code here to let zsh know that there were no
- # completions found by _describe; this is what will trigger other
- # matching algorithms to attempt to find completions.
- # For example zsh can match letters in the middle of words.
- return 1
- else
- # Perform file completion
- __%[1]s_debug "Activating file completion"
-
- # We must return the result of this command, so it must be the
- # last command, or else we must store its result to return it.
- _arguments '*:filename:_files'" ${flagPrefix}"
- fi
+ # Perform file completion
+ __%[1]s_debug "activating file completion"
+ _arguments '*:filename:_files'" ${flagPrefix}"
fi
+ else
+ _describe "completions" completions $(echo $flagPrefix)
fi
}
@@ -256,6 +236,5 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then
fi
`, name, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
- ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
- shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp))
+ ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index e19f94abd..3ad53c73c 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -86,7 +86,7 @@ github.com/containers/buildah/pkg/parse
github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/supplemented
github.com/containers/buildah/util
-# github.com/containers/common v0.29.0
+# github.com/containers/common v0.31.0
github.com/containers/common/pkg/apparmor
github.com/containers/common/pkg/apparmor/internal/supported
github.com/containers/common/pkg/auth
@@ -211,6 +211,8 @@ github.com/containers/storage/pkg/truncindex
github.com/containers/storage/pkg/unshare
# github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-iptables/iptables
+# github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
+github.com/coreos/go-systemd/activation
# github.com/coreos/go-systemd/v22 v22.1.0
github.com/coreos/go-systemd/v22/activation
github.com/coreos/go-systemd/v22/daemon
@@ -218,7 +220,7 @@ github.com/coreos/go-systemd/v22/dbus
github.com/coreos/go-systemd/v22/internal/dlopen
github.com/coreos/go-systemd/v22/journal
github.com/coreos/go-systemd/v22/sdjournal
-# github.com/cri-o/ocicni v0.2.1-0.20201102180012-75c612fda1a2 => github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df
+# github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c
github.com/cri-o/ocicni/pkg/ocicni
# github.com/cyphar/filepath-securejoin v0.2.2
github.com/cyphar/filepath-securejoin
@@ -276,6 +278,9 @@ github.com/docker/go-connections/sockets
github.com/docker/go-connections/tlsconfig
# github.com/docker/go-metrics v0.0.1
github.com/docker/go-metrics
+# github.com/docker/go-plugins-helpers v0.0.0-20200102110956-c9a8a2d92ccc
+github.com/docker/go-plugins-helpers/sdk
+github.com/docker/go-plugins-helpers/volume
# github.com/docker/go-units v0.4.0
github.com/docker/go-units
# github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316
@@ -512,7 +517,7 @@ github.com/seccomp/libseccomp-golang
# github.com/sirupsen/logrus v1.7.0
github.com/sirupsen/logrus
github.com/sirupsen/logrus/hooks/syslog
-# github.com/spf13/cobra v1.1.1 => github.com/Luap99/cobra v1.0.1-0.20201110155035-83a59186c706
+# github.com/spf13/cobra v1.1.1
github.com/spf13/cobra
# github.com/spf13/pflag v1.0.5
github.com/spf13/pflag