summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml64
-rwxr-xr-x.tool/lint2
-rw-r--r--cmd/podman/cliconfig/config.go8
-rw-r--r--cmd/podman/generate.go1
-rw-r--r--cmd/podman/generate_systemd.go70
-rw-r--r--cmd/podman/libpodruntime/runtime.go6
-rw-r--r--cmd/podman/play_kube.go6
-rw-r--r--cmd/podman/run_test.go162
-rw-r--r--cmd/podman/runlabel.go4
-rw-r--r--cmd/podman/shared/container.go4
-rw-r--r--cmd/podman/shared/create.go58
-rw-r--r--cmd/podman/shared/create_cli.go184
-rw-r--r--cmd/podman/shared/funcs.go4
-rw-r--r--cmd/podman/shared/funcs_test.go12
-rw-r--r--cmd/podman/shared/parse/parse.go353
-rw-r--r--cmd/podman/shared/parse/parse_test.go99
-rw-r--r--cmd/podman/top.go9
-rw-r--r--cmd/podman/varlink/io.podman.varlink2
-rw-r--r--commands.md2
-rw-r--r--completions/bash/podman27
-rwxr-xr-xcontrib/cirrus/build_vm_images.sh4
-rwxr-xr-xcontrib/cirrus/integration_test.sh8
-rw-r--r--contrib/cirrus/lib.sh14
-rwxr-xr-xcontrib/cirrus/rootless_test.sh4
-rwxr-xr-xcontrib/cirrus/setup_environment.sh4
-rw-r--r--contrib/cirrus/timestamp.awk20
-rwxr-xr-xcontrib/cirrus/unit_test.sh4
-rw-r--r--contrib/spec/podman.spec.in4
-rw-r--r--docs/podman-generate-systemd.1.md69
-rw-r--r--docs/podman-generate.1.md1
-rw-r--r--docs/podman-info.1.md8
-rw-r--r--docs/podman-pod-top.1.md2
-rw-r--r--docs/podman-run.1.md17
-rw-r--r--docs/podman-top.1.md12
-rw-r--r--libpod/container_top_linux.go14
-rw-r--r--libpod/image/pull.go4
-rw-r--r--libpod/options.go24
-rw-r--r--libpod/runtime.go34
-rw-r--r--libpod/runtime_migrate.go5
-rw-r--r--pkg/adapter/containers.go84
-rw-r--r--pkg/adapter/containers_remote.go5
-rw-r--r--pkg/registries/registries.go49
-rw-r--r--pkg/spec/containerconfig.go40
-rw-r--r--pkg/spec/createconfig.go298
-rw-r--r--pkg/spec/spec.go129
-rw-r--r--pkg/spec/spec_test.go103
-rw-r--r--pkg/spec/storage.go792
-rw-r--r--pkg/spec/storage_test.go38
-rw-r--r--pkg/systemdgen/systemdgen.go43
-rw-r--r--pkg/util/mountOpts.go98
-rw-r--r--pkg/util/utils.go31
-rw-r--r--pkg/varlinkapi/generate.go22
-rw-r--r--pkg/varlinkapi/images.go2
-rw-r--r--test/e2e/generate_systemd_test.go74
-rw-r--r--test/e2e/pod_top_test.go6
-rw-r--r--test/e2e/run_test.go57
-rw-r--r--test/e2e/run_volume_test.go107
-rw-r--r--test/e2e/runlabel_test.go19
-rw-r--r--test/e2e/top_test.go28
59 files changed, 1979 insertions, 1375 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 6ab8beda3..0745b1e7b 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -23,6 +23,9 @@ env:
SCRIPT_BASE: "./contrib/cirrus"
PACKER_BASE: "./contrib/cirrus/packer"
CIRRUS_CLONE_DEPTH: 200
+ # Command to prefix every output line with a timestamp
+ # (can't do inline awk script, Cirrus-CI or YAML mangles quoting)
+ TIMESTAMP: "awk --file ${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk"
####
#### Cache-image names to test with
@@ -121,21 +124,24 @@ gating_task:
gate_script:
# N/B: entrypoint.sh resets $GOSRC (same as make clean)
- - '/usr/local/bin/entrypoint.sh install.tools'
- - '/usr/local/bin/entrypoint.sh validate'
- - '/usr/local/bin/entrypoint.sh lint'
- - '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/test/test_dot_cirrus_yaml.py'
+ - '/usr/local/bin/entrypoint.sh install.tools |& ${TIMESTAMP}'
+ - '/usr/local/bin/entrypoint.sh validate |& ${TIMESTAMP}'
+ - '/usr/local/bin/entrypoint.sh lint |& ${TIMESTAMP}'
+ - '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/test/test_dot_cirrus_yaml.py |& ${TIMESTAMP}'
# This task builds Podman with different buildtags to ensure the build does
# not break. It also verifies all sub-commands have man pages.
build_script:
- - '/usr/local/bin/entrypoint.sh podman'
- - 'cd $GOSRC && ./hack/podman-commands.sh'
+ - '/usr/local/bin/entrypoint.sh podman |& ${TIMESTAMP}'
+ - 'cd $GOSRC && ./hack/podman-commands.sh |& ${TIMESTAMP}'
# N/B: need 'clean' so some commited files are re-generated.
- - '/usr/local/bin/entrypoint.sh clean podman-remote'
- - '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp"'
- - '/usr/local/bin/entrypoint.sh podman-remote-darwin'
- - '/usr/local/bin/entrypoint.sh podman-remote-windows'
+ - '/usr/local/bin/entrypoint.sh clean podman-remote |& ${TIMESTAMP}'
+ - '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp" |& ${TIMESTAMP}'
+ - '/usr/local/bin/entrypoint.sh podman-remote-darwin |& ${TIMESTAMP}'
+ - '/usr/local/bin/entrypoint.sh podman-remote-windows |& ${TIMESTAMP}'
+
+ # Verify expected bash environment (-o pipefail)
+ pipefail_enabledscript: 'if /bin/false | /bin/true; then echo "pipefail fault" && exit 72; fi'
on_failure:
failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh'
@@ -162,12 +168,12 @@ vendor_task:
timeout_in: 30m
vendor_script:
- - '/usr/local/bin/entrypoint.sh .install.vndr'
- - '/usr/local/bin/entrypoint.sh vendor'
- - 'cd /go/src/github.com/containers/libpod && ./hack/tree_status.sh'
+ - '/usr/local/bin/entrypoint.sh .install.vndr |& ${TIMESTAMP}'
+ - '/usr/local/bin/entrypoint.sh vendor |& ${TIMESTAMP}'
+ - 'cd /go/src/github.com/containers/libpod && ./hack/tree_status.sh |& ${TIMESTAMP}'
on_failure:
- failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh'
+ failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh |& ${TIMESTAMP}'
build_each_commit_task:
@@ -188,10 +194,10 @@ build_each_commit_task:
timeout_in: 30m
- setup_environment_script: '$SCRIPT_BASE/setup_environment.sh'
+ setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
build_each_commit_script:
- - 'git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH'
- - 'env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH'
+ - 'git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH |& ${TIMESTAMP}'
+ - 'env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH |& ${TIMESTAMP}'
on_failure:
failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh'
@@ -218,7 +224,7 @@ meta_task:
GCPPROJECT: ENCRYPTED[7c80e728e046b1c76147afd156a32c1c57d4a1ac1eab93b7e68e718c61ca8564fc61fef815952b8ae0a64e7034b8fe4f]
CIRRUS_CLONE_DEPTH: 1 # source not used
- script: '/usr/local/bin/entrypoint.sh'
+ script: '/usr/local/bin/entrypoint.sh |& ${TIMESTAMP}'
# This task does the unit and integration testing for every platform
@@ -251,9 +257,9 @@ testing_task:
# Every *_script runs in sequence, for each task. The name prefix is for
# WebUI reference. The values may be strings...
- setup_environment_script: '$SCRIPT_BASE/setup_environment.sh'
- unit_test_script: '$SCRIPT_BASE/unit_test.sh'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh'
+ setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
+ unit_test_script: '$SCRIPT_BASE/unit_test.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log'
journalctl_b_script: 'journalctl -b'
@@ -287,8 +293,8 @@ special_testing_task:
timeout_in: 120m
- setup_environment_script: '$SCRIPT_BASE/setup_environment.sh'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh'
+ setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log'
journalctl_b_script: 'journalctl -b'
@@ -323,8 +329,8 @@ optional_testing_task:
timeout_in: 60m
- setup_environment_script: '$SCRIPT_BASE/setup_environment.sh'
- system_test_script: '$SCRIPT_BASE/system_test.sh'
+ setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
+ system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}'
# Build new cache-images for future PR testing, but only after a PR merge.
@@ -357,8 +363,8 @@ cache_images_task:
scopes:
- compute
- devstorage.full_control
- environment_script: '$SCRIPT_BASE/setup_environment.sh'
- build_vm_images_script: '$SCRIPT_BASE/build_vm_images.sh'
+ environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
+ build_vm_images_script: '$SCRIPT_BASE/build_vm_images.sh |& ${TIMESTAMP}'
# TODO,Continuous Delivery: Automatically open a libpod PR after using 'sed' to replace
# the image_names with the new (just build) images. That will
@@ -372,7 +378,7 @@ cache_images_task:
# - commit_and_create_upstream_pr.sh
on_failure:
- failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh'
+ failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh |& ${TIMESTAMP}'
# Post message to IRC if everything passed
@@ -395,4 +401,4 @@ success_task:
cpu: 1
memory: 1
- success_script: '$SCRIPT_BASE/success.sh'
+ success_script: '$SCRIPT_BASE/success.sh |& ${TIMESTAMP}'
diff --git a/.tool/lint b/.tool/lint
index f7bf81c1d..01f44311d 100755
--- a/.tool/lint
+++ b/.tool/lint
@@ -25,7 +25,6 @@ ${LINTER} \
--deadline=600s --disable-all\
--enable=deadcode\
--enable=errcheck\
- --enable=goconst\
--enable=gofmt\
--enable=golint\
--enable=ineffassign\
@@ -41,7 +40,6 @@ ${LINTER} \
--exclude='duplicate of.*_test.go.*\(dupl\)$'\
--exclude='cmd\/client\/.*\.go.*\(dupl\)$'\
--exclude='libpod\/.*_easyjson.go:.*'\
- --exclude='.* other occurrence\(s\) of "(container|host|tmpfs|unknown)" found in: .*\(goconst\)$'\
--exclude='vendor\/.*'\
--exclude='podman\/.*'\
--exclude='server\/seccomp\/.*\.go.*$'\
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index 43ba7ddc9..b770aaca0 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -136,12 +136,18 @@ type ExportValues struct {
PodmanCommand
Output string
}
-
type GenerateKubeValues struct {
PodmanCommand
Service bool
}
+type GenerateSystemdValues struct {
+ PodmanCommand
+ Name bool
+ RestartPolicy string
+ StopTimeout int
+}
+
type HistoryValues struct {
PodmanCommand
Human bool
diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go
index a0637ecb2..98bfb00a1 100644
--- a/cmd/podman/generate.go
+++ b/cmd/podman/generate.go
@@ -18,6 +18,7 @@ var (
// Commands that are universally implemented
generateCommands = []*cobra.Command{
_containerKubeCommand,
+ _containerSystemdCommand,
}
)
diff --git a/cmd/podman/generate_systemd.go b/cmd/podman/generate_systemd.go
new file mode 100644
index 000000000..b4779e512
--- /dev/null
+++ b/cmd/podman/generate_systemd.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/pkg/adapter"
+ "github.com/containers/libpod/pkg/systemdgen"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ containerSystemdCommand cliconfig.GenerateSystemdValues
+ containerSystemdDescription = `Command generates a systemd unit file for a Podman container
+ `
+ _containerSystemdCommand = &cobra.Command{
+ Use: "systemd [flags] CONTAINER | POD",
+ Short: "Generate a systemd unit file for a Podman container",
+ Long: containerSystemdDescription,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ containerSystemdCommand.InputArgs = args
+ containerSystemdCommand.GlobalFlags = MainGlobalOpts
+ containerSystemdCommand.Remote = remoteclient
+ return generateSystemdCmd(&containerSystemdCommand)
+ },
+ Args: func(cmd *cobra.Command, args []string) error {
+ if len(args) > 1 || len(args) < 1 {
+ return errors.New("provide only one container name or ID")
+ }
+ return nil
+ },
+ Example: `podman generate kube ctrID
+`,
+ }
+)
+
+func init() {
+ containerSystemdCommand.Command = _containerSystemdCommand
+ containerSystemdCommand.SetHelpTemplate(HelpTemplate())
+ containerSystemdCommand.SetUsageTemplate(UsageTemplate())
+ flags := containerSystemdCommand.Flags()
+ flags.BoolVarP(&containerSystemdCommand.Name, "name", "n", false, "use the container name instead of ID")
+ flags.IntVarP(&containerSystemdCommand.StopTimeout, "timeout", "t", -1, "stop timeout override")
+ flags.StringVar(&containerSystemdCommand.RestartPolicy, "restart-policy", "on-failure", "applicable systemd restart-policy")
+}
+
+func generateSystemdCmd(c *cliconfig.GenerateSystemdValues) error {
+ runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ // User input stop timeout must be 0 or greater
+ if c.Flag("timeout").Changed && c.StopTimeout < 0 {
+ return errors.New("timeout value must be 0 or greater")
+ }
+ // Make sure the input restart policy is valid
+ if err := systemdgen.ValidateRestartPolicy(c.RestartPolicy); err != nil {
+ return err
+ }
+
+ unit, err := runtime.GenerateSystemd(c)
+ if err != nil {
+ return err
+ }
+ fmt.Println(unit)
+ return nil
+}
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go
index b03846bbc..b533dc056 100644
--- a/cmd/podman/libpodruntime/runtime.go
+++ b/cmd/podman/libpodruntime/runtime.go
@@ -78,8 +78,6 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber bool,
options = append(options, libpod.WithRenumber())
}
- options = append(options, libpod.WithContext(ctx))
-
// Only set this if the user changes storage config on the command line
if storageSet {
options = append(options, libpod.WithStorageConfig(storageOpts))
@@ -146,7 +144,7 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber bool,
options = append(options, libpod.WithDefaultInfraCommand(infraCommand))
}
if c.Flags().Changed("config") {
- return libpod.NewRuntimeFromConfig(c.GlobalFlags.Config, options...)
+ return libpod.NewRuntimeFromConfig(ctx, c.GlobalFlags.Config, options...)
}
- return libpod.NewRuntime(options...)
+ return libpod.NewRuntime(ctx, options...)
}
diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go
index 967798399..e778bafb9 100644
--- a/cmd/podman/play_kube.go
+++ b/cmd/podman/play_kube.go
@@ -205,7 +205,8 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues, ctx context.Context, runtime *
return pod, errors.Errorf("Directories are the only supported HostPath type")
}
}
- if err := shared.ValidateVolumeHostDir(hostPath.Path); err != nil {
+
+ if err := createconfig.ValidateVolumeHostDir(hostPath.Path); err != nil {
return pod, errors.Wrapf(err, "Error in parsing HostPath in YAML")
}
volumes[volume.Name] = hostPath.Path
@@ -281,7 +282,6 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
// The default for MemorySwappiness is -1, not 0
containerConfig.Resources.MemorySwappiness = -1
- containerConfig.Runtime = runtime
containerConfig.Image = containerYAML.Image
containerConfig.ImageID = newImage.ID()
containerConfig.Name = containerYAML.Name
@@ -352,7 +352,7 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
if !exists {
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
}
- if err := shared.ValidateVolumeCtrDir(volume.MountPath); err != nil {
+ if err := createconfig.ValidateVolumeCtrDir(volume.MountPath); err != nil {
return nil, errors.Wrapf(err, "error in parsing MountPath")
}
containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", host_path, volume.MountPath))
diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go
deleted file mode 100644
index af9e6923c..000000000
--- a/cmd/podman/run_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package main
-
-import (
- "runtime"
- "testing"
-
- "github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/shared"
- "github.com/containers/libpod/pkg/inspect"
- cc "github.com/containers/libpod/pkg/spec"
- "github.com/containers/libpod/pkg/sysinfo"
- "github.com/docker/go-units"
- ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
- spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/spf13/cobra"
- "github.com/stretchr/testify/assert"
-)
-
-var (
- sysInfo = sysinfo.New(true)
- cmd = []string{"podman", "test", "alpine"}
- CLI *cliconfig.PodmanCommand
-)
-
-// generates a mocked ImageData structure based on alpine
-func generateAlpineImageData() *inspect.ImageData {
- config := &ociv1.ImageConfig{
- User: "",
- ExposedPorts: nil,
- Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
- Entrypoint: []string{},
- Cmd: []string{"/bin/sh"},
- Volumes: nil,
- WorkingDir: "",
- Labels: nil,
- StopSignal: "",
- }
-
- data := &inspect.ImageData{
- ID: "e21c333399e0aeedfd70e8827c9fba3f8e9b170ef8a48a29945eb7702bf6aa5f",
- RepoTags: []string{"docker.io/library/alpine:latest"},
- RepoDigests: []string{"docker.io/library/alpine@sha256:5cb04fce748f576d7b72a37850641de8bd725365519673c643ef2d14819b42c6"},
- Comment: "Created:2017-12-01 18:48:48.949613376 +0000",
- Author: "",
- Architecture: "amd64",
- Os: "linux",
- Version: "17.06.2-ce",
- Config: config,
- }
- return data
-}
-
-// sets a global CLI
-func testCmd(c *cobra.Command) error {
- CLI = &cliconfig.PodmanCommand{Command: c}
- return nil
-}
-
-// creates the mocked cli pointing to our create flags
-// global flags like log-level are not implemented
-func createCLI(args []string) *cliconfig.PodmanCommand {
- var testCommand = &cliconfig.PodmanCommand{
- Command: &cobra.Command{
- Use: "test",
- RunE: func(cmd *cobra.Command, args []string) error {
- return testCmd(cmd)
- },
- },
- }
- rootCmd := testCommand
- getCreateFlags(rootCmd)
- rootCmd.ParseFlags(args)
- return rootCmd
-}
-
-func getRuntimeSpec(c *cliconfig.PodmanCommand) (*spec.Spec, error) {
- /*
- TODO: This test has never worked. Need to install content
- runtime, err := getRuntime(c)
- if err != nil {
- return nil, err
- }
- createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData())
- */
- ctx := getContext()
- genericResults := shared.NewIntermediateLayer(c, false)
- createConfig, err := shared.ParseCreateOpts(ctx, &genericResults, nil, "alpine", generateAlpineImageData())
- if err != nil {
- return nil, err
- }
- runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig)
- if err != nil {
- return nil, err
- }
- return runtimeSpec, nil
-}
-
-// TestPIDsLimit verifies the inputted pid-limit is correctly defined in the spec
-func TestPIDsLimit(t *testing.T) {
- // The default configuration of podman enables seccomp, which is not available on non-Linux systems.
- // Thus, any tests that use the default seccomp setting would fail.
- // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result.
- if runtime.GOOS != "linux" {
- t.Skip("seccomp, which is enabled by default, is only supported on Linux")
- }
- if !sysInfo.PidsLimit {
- t.Skip("running test not supported by the host system")
- }
- args := []string{"--pids-limit", "22"}
- a := createCLI(args)
- a.InputArgs = args
- //a.Run(append(cmd, args...))
- runtimeSpec, err := getRuntimeSpec(a)
- if err != nil {
- t.Fatalf(err.Error())
- }
- assert.Equal(t, runtimeSpec.Linux.Resources.Pids.Limit, int64(22))
-}
-
-// TestBLKIOWeightDevice verifies the inputted blkio weigh device is correctly defined in the spec
-func TestBLKIOWeightDevice(t *testing.T) {
- // The default configuration of podman enables seccomp, which is not available on non-Linux systems.
- // Thus, any tests that use the default seccomp setting would fail.
- // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result.
- if runtime.GOOS != "linux" {
- t.Skip("seccomp, which is enabled by default, is only supported on Linux")
- }
- if !sysInfo.BlkioWeightDevice {
- t.Skip("running test not supported by the host system")
- }
- args := []string{"--blkio-weight-device", "/dev/zero:100"}
- a := createCLI(args)
- a.InputArgs = args
- runtimeSpec, err := getRuntimeSpec(a)
- if err != nil {
- t.Fatalf(err.Error())
- }
- assert.Equal(t, *runtimeSpec.Linux.Resources.BlockIO.WeightDevice[0].Weight, uint16(100))
-}
-
-// TestMemorySwap verifies that the inputted memory swap is correctly defined in the spec
-func TestMemorySwap(t *testing.T) {
- // The default configuration of podman enables seccomp, which is not available on non-Linux systems.
- // Thus, any tests that use the default seccomp setting would fail.
- // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result.
- if runtime.GOOS != "linux" {
- t.Skip("seccomp, which is enabled by default, is only supported on Linux")
- }
- if !sysInfo.SwapLimit {
- t.Skip("running test not supported by the host system")
- }
- args := []string{"--memory-swap", "45m", "--memory", "40m"}
- a := createCLI(args)
- a.InputArgs = args
- //a.Run(append(cmd, args...))
- runtimeSpec, err := getRuntimeSpec(a)
- if err != nil {
- t.Fatalf(err.Error())
- }
- mem, _ := units.RAMInBytes("45m")
- assert.Equal(t, *runtimeSpec.Linux.Resources.Memory.Swap, mem)
-}
diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go
index f097cb693..c426817de 100644
--- a/cmd/podman/runlabel.go
+++ b/cmd/podman/runlabel.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -145,7 +146,8 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error {
return errors.Errorf("%s does not have a label of %s", runlabelImage, label)
}
- cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs)
+ globalOpts := util.GetGlobalOpts(c)
+ cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs, globalOpts)
if err != nil {
return err
}
diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go
index 9050fd2b9..fe447d10d 100644
--- a/cmd/podman/shared/container.go
+++ b/cmd/podman/shared/container.go
@@ -883,7 +883,7 @@ func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtim
}
// GenerateRunlabelCommand generates the command that will eventually be execucted by podman
-func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) {
+func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string, globalOpts string) ([]string, []string, error) {
// If no name is provided, we use the image's basename instead
if name == "" {
baseName, err := image.GetImageBaseName(imageName)
@@ -896,7 +896,7 @@ func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]s
if len(extraArgs) > 0 {
runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " "))
}
- cmd, err := GenerateCommand(runLabel, imageName, name)
+ cmd, err := GenerateCommand(runLabel, imageName, name, globalOpts)
if err != nil {
return nil, nil, errors.Wrapf(err, "unable to generate command")
}
diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index eac2d044d..81566326b 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -25,7 +25,6 @@ import (
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/google/shlex"
- spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
@@ -114,6 +113,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
}
}
}
+
createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, data)
if err != nil {
return nil, nil, err
@@ -123,7 +123,16 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
// at this point. The rest is done by WithOptions.
createConfig.HealthCheck = healthCheck
- ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, nil)
+ // TODO: Should be able to return this from ParseCreateOpts
+ var pod *libpod.Pod
+ if createConfig.Pod != "" {
+ pod, err = runtime.LookupPod(createConfig.Pod)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error looking up pod to join")
+ }
+ }
+
+ ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, pod)
if err != nil {
return nil, nil, err
}
@@ -139,7 +148,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
return ctr, createConfig, nil
}
-func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
+func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string, runtime *libpod.Runtime) error {
var (
labelOpts []string
)
@@ -147,7 +156,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
if config.PidMode.IsHost() {
labelOpts = append(labelOpts, label.DisableSecOpt()...)
} else if config.PidMode.IsContainer() {
- ctr, err := config.Runtime.LookupContainer(config.PidMode.Container())
+ ctr, err := runtime.LookupContainer(config.PidMode.Container())
if err != nil {
return errors.Wrapf(err, "container %q not found", config.PidMode.Container())
}
@@ -161,7 +170,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
if config.IpcMode.IsHost() {
labelOpts = append(labelOpts, label.DisableSecOpt()...)
} else if config.IpcMode.IsContainer() {
- ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container())
+ ctr, err := runtime.LookupContainer(config.IpcMode.Container())
if err != nil {
return errors.Wrapf(err, "container %q not found", config.IpcMode.Container())
}
@@ -331,18 +340,6 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
}
blkioWeight = uint16(u)
}
- var mountList []spec.Mount
- if mountList, err = parseMounts(c.StringArray("mount")); err != nil {
- return nil, err
- }
-
- if err = parseVolumes(c.StringArray("volume")); err != nil {
- return nil, err
- }
-
- if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil {
- return nil, err
- }
tty := c.Bool("tty")
@@ -604,7 +601,6 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
memorySwappiness := c.Int64("memory-swappiness")
config := &cc.CreateConfig{
- Runtime: runtime,
Annotations: annotations,
BuiltinImgVolumes: ImageVolumes,
ConmonPidFile: c.String("conmon-pidfile"),
@@ -627,6 +623,8 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
HTTPProxy: c.Bool("http-proxy"),
NoHosts: c.Bool("no-hosts"),
IDMappings: idmappings,
+ Init: c.Bool("init"),
+ InitPath: c.String("init-path"),
Image: imageName,
ImageID: imageID,
Interactive: c.Bool("interactive"),
@@ -687,31 +685,18 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
Tty: tty,
User: user,
UsernsMode: usernsMode,
- Mounts: mountList,
+ MountsFlag: c.StringArray("mount"),
Volumes: c.StringArray("volume"),
WorkDir: workDir,
Rootfs: rootfs,
VolumesFrom: c.StringSlice("volumes-from"),
Syslog: c.Bool("syslog"),
}
- if c.Bool("init") {
- initPath := c.String("init-path")
- if initPath == "" {
- rtc, err := runtime.GetConfig()
- if err != nil {
- return nil, err
- }
- initPath = rtc.InitPath
- }
- if err := config.AddContainerInitBinary(initPath); err != nil {
- return nil, err
- }
- }
if config.Privileged {
config.LabelOpts = label.DisableSecOpt()
} else {
- if err := parseSecurityOpt(config, c.StringArray("security-opt")); err != nil {
+ if err := parseSecurityOpt(config, c.StringArray("security-opt"), runtime); err != nil {
return nil, err
}
}
@@ -727,12 +712,7 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
}
func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) {
- runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig)
- if err != nil {
- return nil, err
- }
-
- options, err := createConfig.GetContainerCreateOptions(r, pod)
+ runtimeSpec, options, err := createConfig.MakeContainerConfig(r, pod)
if err != nil {
return nil, err
}
diff --git a/cmd/podman/shared/create_cli.go b/cmd/podman/shared/create_cli.go
index 4f9cb1699..f731e8db5 100644
--- a/cmd/podman/shared/create_cli.go
+++ b/cmd/podman/shared/create_cli.go
@@ -2,15 +2,11 @@ package shared
import (
"fmt"
- "os"
- "path/filepath"
"strings"
"github.com/containers/libpod/cmd/podman/shared/parse"
cc "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/sysinfo"
- "github.com/docker/go-units"
- spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -78,186 +74,6 @@ func addWarning(warnings []string, msg string) []string {
return append(warnings, msg)
}
-// Format supported.
-// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
-// podman run --mount type=tmpfs,target=/dev/shm ..
-func parseMounts(mounts []string) ([]spec.Mount, error) {
- // TODO(vrothberg): the manual parsing can be replaced with a regular expression
- // to allow a more robust parsing of the mount format and to give
- // precise errors regarding supported format versus suppored options.
- var mountList []spec.Mount
- errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]")
- for _, mount := range mounts {
- var tokenCount int
- var mountInfo spec.Mount
-
- arr := strings.SplitN(mount, ",", 2)
- if len(arr) < 2 {
- return nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
- }
- kv := strings.Split(arr[0], "=")
- if kv[0] != "type" {
- return nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
- }
- switch kv[1] {
- case "bind":
- mountInfo.Type = string(cc.TypeBind)
- case "tmpfs":
- mountInfo.Type = string(cc.TypeTmpfs)
- mountInfo.Source = string(cc.TypeTmpfs)
- mountInfo.Options = append(mountInfo.Options, []string{"rprivate", "noexec", "nosuid", "nodev", "size=65536k"}...)
-
- default:
- return nil, errors.Errorf("invalid filesystem type %q", kv[1])
- }
-
- tokens := strings.Split(arr[1], ",")
- for i, val := range tokens {
- if i == (tokenCount - 1) {
- //Parse tokens before options.
- break
- }
- kv := strings.Split(val, "=")
- switch kv[0] {
- case "ro", "nosuid", "nodev", "noexec":
- mountInfo.Options = append(mountInfo.Options, kv[0])
- case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
- if mountInfo.Type != "bind" {
- return nil, errors.Errorf("%s can only be used with bind mounts", kv[0])
- }
- mountInfo.Options = append(mountInfo.Options, kv[0])
- case "tmpfs-mode":
- if mountInfo.Type != "tmpfs" {
- return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0])
- }
- mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("mode=%s", kv[1]))
- case "tmpfs-size":
- if mountInfo.Type != "tmpfs" {
- return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0])
- }
- shmSize, err := units.FromHumanSize(kv[1])
- if err != nil {
- return nil, errors.Wrapf(err, "unable to translate tmpfs-size")
- }
-
- mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("size=%d", shmSize))
-
- case "bind-propagation":
- if mountInfo.Type != "bind" {
- return nil, errors.Errorf("%s can only be used with bind mounts", kv[0])
- }
- mountInfo.Options = append(mountInfo.Options, kv[1])
- case "src", "source":
- if mountInfo.Type == "tmpfs" {
- return nil, errors.Errorf("cannot use src= on a tmpfs file system")
- }
- if err := ValidateVolumeHostDir(kv[1]); err != nil {
- return nil, err
- }
- mountInfo.Source = kv[1]
- case "target", "dst", "destination":
- if err := ValidateVolumeCtrDir(kv[1]); err != nil {
- return nil, err
- }
- mountInfo.Destination = kv[1]
- default:
- return nil, errors.Errorf("incorrect mount option : %s", kv[0])
- }
- }
- mountList = append(mountList, mountInfo)
- }
- return mountList, nil
-}
-
-func parseVolumes(volumes []string) error {
- for _, volume := range volumes {
- arr := strings.SplitN(volume, ":", 3)
- if len(arr) < 2 {
- return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume)
- }
- if err := ValidateVolumeHostDir(arr[0]); err != nil {
- return err
- }
- if err := ValidateVolumeCtrDir(arr[1]); err != nil {
- return err
- }
- if len(arr) > 2 {
- if err := validateVolumeOpts(arr[2]); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-func parseVolumesFrom(volumesFrom []string) error {
- for _, vol := range volumesFrom {
- arr := strings.SplitN(vol, ":", 2)
- if len(arr) == 2 {
- if strings.Contains(arr[1], "Z") || strings.Contains(arr[1], "private") || strings.Contains(arr[1], "slave") || strings.Contains(arr[1], "shared") {
- return errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", arr[1])
- }
- if err := validateVolumeOpts(arr[1]); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// ValidateVolumeHostDir ...
-func ValidateVolumeHostDir(hostDir string) error {
- if len(hostDir) == 0 {
- return errors.Errorf("host directory cannot be empty")
- }
- if filepath.IsAbs(hostDir) {
- if _, err := os.Stat(hostDir); err != nil {
- return errors.Wrapf(err, "error checking path %q", hostDir)
- }
- }
- // If hostDir is not an absolute path, that means the user wants to create a
- // named volume. This will be done later on in the code.
- return nil
-}
-
-// ValidateVolumeCtrDir ...
-func ValidateVolumeCtrDir(ctrDir string) error {
- if len(ctrDir) == 0 {
- return errors.Errorf("container directory cannot be empty")
- }
- if !filepath.IsAbs(ctrDir) {
- return errors.Errorf("invalid container path, must be an absolute path %q", ctrDir)
- }
- return nil
-}
-
-func validateVolumeOpts(option string) error {
- var foundRootPropagation, foundRWRO, foundLabelChange int
- options := strings.Split(option, ",")
- for _, opt := range options {
- switch opt {
- case "rw", "ro":
- foundRWRO++
- if foundRWRO > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", option)
- }
- case "z", "Z":
- foundLabelChange++
- if foundLabelChange > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option)
- }
- case "private", "rprivate", "shared", "rshared", "slave", "rslave":
- foundRootPropagation++
- if foundRootPropagation > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", option)
- }
- default:
- return errors.Errorf("invalid option type %q", option)
- }
- }
- return nil
-}
-
func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
warnings := []string{}
sysInfo := sysinfo.New(true)
diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go
index 70d041fd2..c189cceeb 100644
--- a/cmd/podman/shared/funcs.go
+++ b/cmd/podman/shared/funcs.go
@@ -41,7 +41,7 @@ func substituteCommand(cmd string) (string, error) {
}
// GenerateCommand takes a label (string) and converts it to an executable command
-func GenerateCommand(command, imageName, name string) ([]string, error) {
+func GenerateCommand(command, imageName, name, globalOpts string) ([]string, error) {
var (
newCommand []string
)
@@ -79,6 +79,8 @@ func GenerateCommand(command, imageName, name string) ([]string, error) {
newArg = fmt.Sprintf("NAME=%s", name)
case "$NAME":
newArg = name
+ case "$GLOBAL_OPTS":
+ newArg = globalOpts
default:
newArg = arg
}
diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go
index 7506b9d9c..c05348242 100644
--- a/cmd/podman/shared/funcs_test.go
+++ b/cmd/podman/shared/funcs_test.go
@@ -20,7 +20,7 @@ var (
func TestGenerateCommand(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo \"hello world\""
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo hello world"
- newCommand, err := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "bar", "")
assert.Nil(t, err)
assert.Equal(t, "hello world", newCommand[11])
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
@@ -83,7 +83,7 @@ func TestGenerateCommandCheckSubstitution(t *testing.T) {
}
for _, test := range tests {
- newCommand, err := GenerateCommand(test.input, "foo", "bar")
+ newCommand, err := GenerateCommand(test.input, "foo", "bar", "")
if test.shouldFail {
assert.NotNil(t, err)
} else {
@@ -96,14 +96,14 @@ func TestGenerateCommandCheckSubstitution(t *testing.T) {
func TestGenerateCommandPath(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand, _ := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, _ := GenerateCommand(inputCommand, "foo", "bar", "")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandNoSetName(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name foo -e NAME=foo -e IMAGE=foo foo echo install"
- newCommand, err := GenerateCommand(inputCommand, "foo", "")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "", "")
assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
@@ -111,7 +111,7 @@ func TestGenerateCommandNoSetName(t *testing.T) {
func TestGenerateCommandNoName(t *testing.T) {
inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install"
- newCommand, err := GenerateCommand(inputCommand, "foo", "")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "", "")
assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
@@ -119,7 +119,7 @@ func TestGenerateCommandNoName(t *testing.T) {
func TestGenerateCommandAlreadyPodman(t *testing.T) {
inputCommand := "podman run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand, err := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "bar", "")
assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
diff --git a/cmd/podman/shared/parse/parse.go b/cmd/podman/shared/parse/parse.go
index a3751835b..7bc2652cb 100644
--- a/cmd/podman/shared/parse/parse.go
+++ b/cmd/podman/shared/parse/parse.go
@@ -5,15 +5,10 @@ package parse
import (
"bufio"
- "bytes"
- "encoding/json"
"fmt"
- "io/ioutil"
"net"
"os"
- "path"
"regexp"
- "strconv"
"strings"
"github.com/pkg/errors"
@@ -72,77 +67,6 @@ func validateIPAddress(val string) (string, error) {
return "", fmt.Errorf("%s is not an ip address", val)
}
-// validateAttach validates that the specified string is a valid attach option.
-// for attach flag
-func validateAttach(val string) (string, error) { //nolint
- s := strings.ToLower(val)
- for _, str := range []string{"stdin", "stdout", "stderr"} {
- if s == str {
- return s, nil
- }
- }
- return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
-}
-
-// validate the blkioWeight falls in the range of 10 to 1000
-// for blkio-weight flag
-func validateBlkioWeight(val int64) (int64, error) { //nolint
- if val >= 10 && val <= 1000 {
- return val, nil
- }
- return -1, errors.Errorf("invalid blkio weight %q, should be between 10 and 1000", val)
-}
-
-func validatePath(val string, validator func(string) bool) (string, error) {
- var containerPath string
- var mode string
-
- if strings.Count(val, ":") > 2 {
- return val, fmt.Errorf("bad format for path: %s", val)
- }
-
- split := strings.SplitN(val, ":", 3)
- if split[0] == "" {
- return val, fmt.Errorf("bad format for path: %s", val)
- }
- switch len(split) {
- case 1:
- containerPath = split[0]
- val = path.Clean(containerPath)
- case 2:
- if isValid := validator(split[1]); isValid {
- containerPath = split[0]
- mode = split[1]
- val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
- } else {
- containerPath = split[1]
- val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
- }
- case 3:
- containerPath = split[1]
- mode = split[2]
- if isValid := validator(split[2]); !isValid {
- return val, fmt.Errorf("bad mode specified: %s", mode)
- }
- val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
- }
-
- if !path.IsAbs(containerPath) {
- return val, fmt.Errorf("%s is not an absolute path", containerPath)
- }
- return val, nil
-}
-
-// validateDNSSearch validates domain for resolvconf search configuration.
-// A zero length domain is represented by a dot (.).
-// for dns-search flag
-func validateDNSSearch(val string) (string, error) { //nolint
- if val = strings.Trim(val, " "); val == "." {
- return val, nil
- }
- return ValidateDomain(val)
-}
-
func ValidateDomain(val string) (string, error) {
if alphaRegexp.FindString(val) == "" {
return "", fmt.Errorf("%s is not a valid domain", val)
@@ -154,30 +78,6 @@ func ValidateDomain(val string) (string, error) {
return "", fmt.Errorf("%s is not a valid domain", val)
}
-// validateEnv validates an environment variable and returns it.
-// If no value is specified, it returns the current value using os.Getenv.
-// for env flag
-func validateEnv(val string) (string, error) { //nolint
- arr := strings.Split(val, "=")
- if len(arr) > 1 {
- return val, nil
- }
- if !doesEnvExist(val) {
- return val, nil
- }
- return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
-}
-
-func doesEnvExist(name string) bool {
- for _, entry := range os.Environ() {
- parts := strings.SplitN(entry, "=", 2)
- if parts[0] == name {
- return true
- }
- }
- return false
-}
-
// reads a file of line terminated key=value pairs, and overrides any keys
// present in the file with additional pairs specified in the override parameter
// for env-file and labels-file flags
@@ -241,259 +141,6 @@ func parseEnvFile(env map[string]string, filename string) error {
return scanner.Err()
}
-// validateLabel validates that the specified string is a valid label, and returns it.
-// Labels are in the form on key=value.
-// for label flag
-func validateLabel(val string) (string, error) { //nolint
- if strings.Count(val, "=") < 1 {
- return "", fmt.Errorf("bad attribute format: %s", val)
- }
- return val, nil
-}
-
-// validateMACAddress validates a MAC address.
-// for mac-address flag
-func validateMACAddress(val string) (string, error) { //nolint
- _, err := net.ParseMAC(strings.TrimSpace(val))
- if err != nil {
- return "", err
- }
- return val, nil
-}
-
-// parseLoggingOpts validates the logDriver and logDriverOpts
-// for log-opt and log-driver flags
-func parseLoggingOpts(logDriver string, logDriverOpt []string) (map[string]string, error) { //nolint
- logOptsMap := convertKVStringsToMap(logDriverOpt)
- if logDriver == "none" && len(logDriverOpt) > 0 {
- return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", logDriver)
- }
- return logOptsMap, nil
-}
-
-// parsePortSpecs receives port specs in the format of ip:public:private/proto and parses
-// these in to the internal types
-// for publish, publish-all, and expose flags
-func parsePortSpecs(ports []string) ([]*PortMapping, error) { //nolint
- var portMappings []*PortMapping
- for _, rawPort := range ports {
- portMapping, err := parsePortSpec(rawPort)
- if err != nil {
- return nil, err
- }
-
- portMappings = append(portMappings, portMapping...)
- }
- return portMappings, nil
-}
-
-func validateProto(proto string) bool {
- for _, availableProto := range []string{"tcp", "udp"} {
- if availableProto == proto {
- return true
- }
- }
- return false
-}
-
-// parsePortSpec parses a port specification string into a slice of PortMappings
-func parsePortSpec(rawPort string) ([]*PortMapping, error) {
- var proto string
- rawIP, hostPort, containerPort := splitParts(rawPort)
- proto, containerPort = splitProtoPort(containerPort)
-
- // Strip [] from IPV6 addresses
- ip, _, err := net.SplitHostPort(rawIP + ":")
- if err != nil {
- return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err)
- }
- if ip != "" && net.ParseIP(ip) == nil {
- return nil, fmt.Errorf("Invalid ip address: %s", ip)
- }
- if containerPort == "" {
- return nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
- }
-
- startPort, endPort, err := parsePortRange(containerPort)
- if err != nil {
- return nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
- }
-
- var startHostPort, endHostPort uint64 = 0, 0
- if len(hostPort) > 0 {
- startHostPort, endHostPort, err = parsePortRange(hostPort)
- if err != nil {
- return nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
- }
- }
-
- if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
- // Allow host port range iff containerPort is not a range.
- // In this case, use the host port range as the dynamic
- // host port range to allocate into.
- if endPort != startPort {
- return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
- }
- }
-
- if !validateProto(strings.ToLower(proto)) {
- return nil, fmt.Errorf("invalid proto: %s", proto)
- }
-
- protocol := Protocol_TCP
- if strings.ToLower(proto) == "udp" {
- protocol = Protocol_UDP
- }
-
- var ports []*PortMapping
- for i := uint64(0); i <= (endPort - startPort); i++ {
- containerPort = strconv.FormatUint(startPort+i, 10)
- if len(hostPort) > 0 {
- hostPort = strconv.FormatUint(startHostPort+i, 10)
- }
- // Set hostPort to a range only if there is a single container port
- // and a dynamic host port.
- if startPort == endPort && startHostPort != endHostPort {
- hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
- }
-
- ctrPort, err := strconv.ParseInt(containerPort, 10, 32)
- if err != nil {
- return nil, err
- }
- hPort, err := strconv.ParseInt(hostPort, 10, 32)
- if err != nil {
- return nil, err
- }
-
- port := &PortMapping{
- Protocol: protocol,
- ContainerPort: int32(ctrPort),
- HostPort: int32(hPort),
- HostIp: ip,
- }
-
- ports = append(ports, port)
- }
- return ports, nil
-}
-
-// parsePortRange parses and validates the specified string as a port-range (8000-9000)
-func parsePortRange(ports string) (uint64, uint64, error) {
- if ports == "" {
- return 0, 0, fmt.Errorf("empty string specified for ports")
- }
- if !strings.Contains(ports, "-") {
- start, err := strconv.ParseUint(ports, 10, 16)
- end := start
- return start, end, err
- }
-
- parts := strings.Split(ports, "-")
- start, err := strconv.ParseUint(parts[0], 10, 16)
- if err != nil {
- return 0, 0, err
- }
- end, err := strconv.ParseUint(parts[1], 10, 16)
- if err != nil {
- return 0, 0, err
- }
- if end < start {
- return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
- }
- return start, end, nil
-}
-
-// splitParts separates the different parts of rawPort
-func splitParts(rawport string) (string, string, string) {
- parts := strings.Split(rawport, ":")
- n := len(parts)
- containerport := parts[n-1]
-
- switch n {
- case 1:
- return "", "", containerport
- case 2:
- return "", parts[0], containerport
- case 3:
- return parts[0], parts[1], containerport
- default:
- return strings.Join(parts[:n-2], ":"), parts[n-2], containerport
- }
-}
-
-// splitProtoPort splits a port in the format of port/proto
-func splitProtoPort(rawPort string) (string, string) {
- parts := strings.Split(rawPort, "/")
- l := len(parts)
- if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
- return "", ""
- }
- if l == 1 {
- return "tcp", rawPort
- }
- if len(parts[1]) == 0 {
- return "tcp", parts[0]
- }
- return parts[1], parts[0]
-}
-
-// takes a local seccomp file and reads its file contents
-// for security-opt flag
-func parseSecurityOpts(securityOpts []string) ([]string, error) { //nolint
- for key, opt := range securityOpts {
- con := strings.SplitN(opt, "=", 2)
- if len(con) == 1 && con[0] != "no-new-privileges" {
- if strings.Index(opt, ":") != -1 {
- con = strings.SplitN(opt, ":", 2)
- } else {
- return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
- }
- }
- if con[0] == "seccomp" && con[1] != "unconfined" {
- f, err := ioutil.ReadFile(con[1])
- if err != nil {
- return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
- }
- b := bytes.NewBuffer(nil)
- if err := json.Compact(b, f); err != nil {
- return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
- }
- securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
- }
- }
-
- return securityOpts, nil
-}
-
-// convertKVStringsToMap converts ["key=value"] to {"key":"value"}
-func convertKVStringsToMap(values []string) map[string]string {
- result := make(map[string]string, len(values))
- for _, value := range values {
- kv := strings.SplitN(value, "=", 2)
- if len(kv) == 1 {
- result[kv[0]] = ""
- } else {
- result[kv[0]] = kv[1]
- }
- }
-
- return result
-}
-
-// Takes a stringslice and converts to a uint32slice
-func stringSlicetoUint32Slice(inputSlice []string) ([]uint32, error) {
- var outputSlice []uint32
- for _, v := range inputSlice {
- u, err := strconv.ParseUint(v, 10, 32)
- if err != nil {
- return outputSlice, err
- }
- outputSlice = append(outputSlice, uint32(u))
- }
- return outputSlice, nil
-}
-
// ValidateFileName returns an error if filename contains ":"
// as it is currently not supported
func ValidateFileName(filename string) error {
diff --git a/cmd/podman/shared/parse/parse_test.go b/cmd/podman/shared/parse/parse_test.go
new file mode 100644
index 000000000..0a221c244
--- /dev/null
+++ b/cmd/podman/shared/parse/parse_test.go
@@ -0,0 +1,99 @@
+//nolint
+// most of these validate and parse functions have been taken from projectatomic/docker
+// and modified for cri-o
+package parse
+
+import (
+ "testing"
+)
+
+func TestValidateExtraHost(t *testing.T) {
+ type args struct {
+ val string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ //2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ {name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false},
+ {name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true},
+ {name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true},
+ {name: "noname-ipv4", args: args{val: "192.168.1.1"}, want: "", wantErr: true},
+ {name: "noname-ipv4", args: args{val: ":192.168.1.1"}, want: "", wantErr: true},
+ {name: "noip", args: args{val: "foobar:"}, want: "", wantErr: true},
+ {name: "noip", args: args{val: "foobar"}, want: "", wantErr: true},
+ {name: "good-ipv6", args: args{val: "foobar:2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "foobar:2001:0db8:85a3:0000:0000:8a2e:0370:7334", wantErr: false},
+ {name: "bad-ipv6", args: args{val: "foobar:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true},
+ {name: "bad-ipv6", args: args{val: "foobar:0db8:85a3:0000:0000:8a2e:0370:7334.0000.0000.000"}, want: "", wantErr: true},
+ {name: "noname-ipv6", args: args{val: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true},
+ {name: "noname-ipv6", args: args{val: ":2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ValidateExtraHost(tt.args.val)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ValidateExtraHost() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("ValidateExtraHost() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_validateIPAddress(t *testing.T) {
+ type args struct {
+ val string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ {name: "ipv4-good", args: args{val: "192.168.1.1"}, want: "192.168.1.1", wantErr: false},
+ {name: "ipv4-bad", args: args{val: "192.168.1.1.1"}, want: "", wantErr: true},
+ {name: "ipv4-bad", args: args{val: "192."}, want: "", wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := validateIPAddress(tt.args.val)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("validateIPAddress() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("validateIPAddress() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestValidateFileName(t *testing.T) {
+ type args struct {
+ filename string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ }{
+ {name: "good", args: args{filename: "/som/rand/path"}, wantErr: false},
+ {name: "good", args: args{filename: "som/rand/path"}, wantErr: false},
+ {name: "good", args: args{filename: "/"}, wantErr: false},
+ {name: "bad", args: args{filename: "/:"}, wantErr: true},
+ {name: "bad", args: args{filename: ":/"}, wantErr: true},
+ {name: "bad", args: args{filename: "/some/rand:/path"}, wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := ValidateFileName(tt.args.filename); (err != nil) != tt.wantErr {
+ t.Errorf("ValidateFileName() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/cmd/podman/top.go b/cmd/podman/top.go
index 2e0a22d92..8583eccb5 100644
--- a/cmd/podman/top.go
+++ b/cmd/podman/top.go
@@ -33,7 +33,7 @@ var (
%s`, getDescriptorString())
_topCommand = &cobra.Command{
- Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS]",
+ Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS|ARGS]",
Short: "Display the running processes of a container",
Long: topDescription,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -42,9 +42,11 @@ var (
topCommand.Remote = remoteclient
return topCmd(&topCommand)
},
+ Args: cobra.ArbitraryArgs,
Example: `podman top ctrID
- podman top --latest
- podman top ctrID pid seccomp args %C`,
+podman top --latest
+podman top ctrID pid seccomp args %C
+podman top ctrID -eo user,pid,comm`,
}
)
@@ -53,6 +55,7 @@ func init() {
topCommand.SetHelpTemplate(HelpTemplate())
topCommand.SetUsageTemplate(UsageTemplate())
flags := topCommand.Flags()
+ flags.SetInterspersed(false)
flags.BoolVar(&topCommand.ListDescriptors, "list-descriptors", false, "")
flags.MarkHidden("list-descriptors")
flags.BoolVarP(&topCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 912d001e9..ace81646c 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -1218,6 +1218,8 @@ method GetLayersMapWithImageInfo() -> (layerMap: string)
# BuildImageHierarchyMap is for the development of Podman and should not be used.
method BuildImageHierarchyMap(name: string) -> (imageInfo: string)
+method GenerateSystemd(name: string, restart: string, timeout: int, useName: bool) -> (unit: string)
+
# ImageNotFound means the image could not be found by the provided name or ID in local storage.
error ImageNotFound (id: string, reason: string)
diff --git a/commands.md b/commands.md
index 88290dc1d..e6c211254 100644
--- a/commands.md
+++ b/commands.md
@@ -25,6 +25,8 @@ Command | Descr
[podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container |
[podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |
[podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods |
+[podman-generate-kube(1)](/docs/podman-generate-kube.1.md) | Generate Kubernetes YAML based on a container or Pod |
+[podman-generate-systemd(1)](/docs/podman-generate-systemd.1.md) | Generate a Systemd unit file for a container |
[podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |
[podman-image(1)](/docs/podman-image.1.md) | Manage Images |
[podman-image-exists(1)](/docs/podman-image-exists.1.md) | Check if an image exists in local storage |
diff --git a/completions/bash/podman b/completions/bash/podman
index a02a47190..e3c0c1dbf 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -919,6 +919,7 @@ _podman_generate() {
"
subcommands="
kube
+ systemd
"
__podman_subcommands "$subcommands $aliases" && return
@@ -2460,6 +2461,32 @@ _podman_generate_kube() {
esac
}
+_podman_generate_systemd() {
+ local options_with_args="
+ --restart-policy
+ -t
+ --timeout"
+
+ local boolean_options="
+ -h
+ --help
+ -n
+ --name
+ "
+
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "
+ $(__podman_containers --all)
+ " -- "$cur" ) )
+ __ltrim_colon_completions "$cur"
+ ;;
+ esac
+}
+
_podman_play_kube() {
local options_with_args="
--authfile
diff --git a/contrib/cirrus/build_vm_images.sh b/contrib/cirrus/build_vm_images.sh
index fc8cbb404..43eb3c057 100755
--- a/contrib/cirrus/build_vm_images.sh
+++ b/contrib/cirrus/build_vm_images.sh
@@ -5,8 +5,6 @@ source $(dirname $0)/lib.sh
req_env_var CNI_COMMIT CRIO_COMMIT RUNC_COMMIT PACKER_BUILDS BUILT_IMAGE_SUFFIX CENTOS_BASE_IMAGE UBUNTU_BASE_IMAGE FEDORA_BASE_IMAGE FAH_BASE_IMAGE RHEL_BASE_IMAGE RHSM_COMMAND SERVICE_ACCOUNT GCE_SSH_USERNAME GCP_PROJECT_ID PACKER_VER SCRIPT_BASE PACKER_BASE
-record_timestamp "cache-image build start"
-
show_env_vars
# Everything here is running on the 'image-builder-image' GCE image
@@ -43,8 +41,6 @@ make libpod_images \
POST_MERGE_BUCKET_SUFFIX=$POST_MERGE_BUCKET_SUFFIX \
BUILT_IMAGE_SUFFIX=$BUILT_IMAGE_SUFFIX
-record_timestamp "cache-image build end"
-
# When successful, upload manifest of produced images using a filename unique
# to this build.
URI="gs://packer-import${POST_MERGE_BUCKET_SUFFIX}/manifest${BUILT_IMAGE_SUFFIX}.json"
diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh
index 71223803c..5b73f0c6c 100755
--- a/contrib/cirrus/integration_test.sh
+++ b/contrib/cirrus/integration_test.sh
@@ -5,14 +5,6 @@ source $(dirname $0)/lib.sh
req_env_var GOSRC SCRIPT_BASE OS_RELEASE_ID OS_RELEASE_VER CONTAINER_RUNTIME
-exit_handler() {
- set +ex
- record_timestamp "integration test end"
-}
-trap exit_handler EXIT
-
-record_timestamp "integration test start"
-
cd "$GOSRC"
if [[ "$SPECIALMODE" == "in_podman" ]]
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index 1c459ebc7..f422fe935 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -17,7 +17,6 @@ PACKER_BASE=${PACKER_BASE:-./contrib/cirrus/packer}
CIRRUS_BUILD_ID=${CIRRUS_BUILD_ID:-DEADBEEF} # a human
CIRRUS_BASE_SHA=${CIRRUS_BASE_SHA:-HEAD}
CIRRUS_CHANGE_IN_REPO=${CIRRUS_CHANGE_IN_REPO:-FETCH_HEAD}
-TIMESTAMPS_FILEPATH="${TIMESTAMPS_FILEPATH:-/var/tmp/timestamps}"
SPECIALMODE="${SPECIALMODE:-none}"
export CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-podman}
@@ -153,16 +152,6 @@ ircmsg() {
set -e
}
-record_timestamp() {
- set +x # sometimes it's turned on
- req_env_var TIMESTAMPS_FILEPATH
- echo "." # cirrus webui strips blank-lines
- STAMPMSG="The $1 time at the tone will be:"
- echo -e "$STAMPMSG\t$(date --iso-8601=seconds)" | \
- tee -a $TIMESTAMPS_FILEPATH
- echo -e "BLEEEEEEEEEEP!\n."
-}
-
setup_rootless() {
req_env_var ROOTLESS_USER GOSRC ENVLIB
@@ -203,9 +192,6 @@ setup_rootless() {
echo "${ROOTLESS_USER}:$[ROOTLESS_UID * 100]:65536" | \
tee -a /etc/subuid >> /etc/subgid
- echo "Setting permissions on automation files"
- chmod 666 "$TIMESTAMPS_FILEPATH"
-
echo "Copying $HOME/$ENVLIB"
install -o $ROOTLESS_USER -g $ROOTLESS_USER -m 0700 \
"$HOME/$ENVLIB" "/home/$ROOTLESS_USER/$ENVLIB"
diff --git a/contrib/cirrus/rootless_test.sh b/contrib/cirrus/rootless_test.sh
index 2803d4318..8351214f3 100755
--- a/contrib/cirrus/rootless_test.sh
+++ b/contrib/cirrus/rootless_test.sh
@@ -19,13 +19,9 @@ export PODMAN_VARLINK_ADDRESS=unix:/tmp/podman-$(id -u)
echo "."
echo "Hello, my name is $USER and I live in $PWD can I be your friend?"
-record_timestamp "rootless test start"
-
cd "$GOSRC"
make
make varlink_generate
make test-binaries
make ginkgo
make ginkgo-remote
-
-record_timestamp "rootless test end"
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index 3bc6c2290..2f9b1d796 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -4,8 +4,6 @@ set -e
source $(dirname $0)/lib.sh
-record_timestamp "env. setup start"
-
req_env_var USER HOME ENVLIB SCRIPT_BASE CIRRUS_BUILD_ID
[[ "$SHELL" =~ "bash" ]] || chsh -s /bin/bash
@@ -86,5 +84,3 @@ then
fi
show_env_vars
-
-record_timestamp "env. setup end"
diff --git a/contrib/cirrus/timestamp.awk b/contrib/cirrus/timestamp.awk
new file mode 100644
index 000000000..95b312e51
--- /dev/null
+++ b/contrib/cirrus/timestamp.awk
@@ -0,0 +1,20 @@
+
+
+# This script is intended to be piped into by automation, in order to
+# mark output lines with timing information. For example:
+# /path/to/command |& awk --file timestamp.awk
+
+BEGIN {
+ STARTTIME=systime()
+ printf "[%s] START", strftime("%T")
+ printf " - All [+xxxx] lines that follow are relative to right now.\n"
+}
+
+{
+ printf "[%+05ds] %s\n", systime()-STARTTIME, $0
+}
+
+END {
+ printf "[%s] END", strftime("%T")
+ printf " - [%+05ds] total duration since START\n", systime()-STARTTIME
+}
diff --git a/contrib/cirrus/unit_test.sh b/contrib/cirrus/unit_test.sh
index a0964061f..0e8c9e2e2 100755
--- a/contrib/cirrus/unit_test.sh
+++ b/contrib/cirrus/unit_test.sh
@@ -5,8 +5,6 @@ source $(dirname $0)/lib.sh
req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER
-record_timestamp "unit test start"
-
clean_env
set -x
@@ -14,5 +12,3 @@ cd "$GOSRC"
make install.tools
make localunit
make
-
-record_timestamp "unit test end"
diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in
index 4b9cbd4cf..f3ee01bca 100644
--- a/contrib/spec/podman.spec.in
+++ b/contrib/spec/podman.spec.in
@@ -204,7 +204,7 @@ BuildRequires: golang(github.com/containers/image/docker/tarfile)
BuildRequires: golang(github.com/containers/image/image)
BuildRequires: golang(github.com/containers/image/oci/archive)
BuildRequires: golang(github.com/containers/image/pkg/strslice)
-BuildRequires: golang(github.com/containers/image/pkg/sysregistries)
+BuildRequires: golang(github.com/containers/image/pkg/sysregistriesv2)
BuildRequires: golang(github.com/containers/image/signature)
BuildRequires: golang(github.com/containers/image/storage)
BuildRequires: golang(github.com/containers/image/tarball)
@@ -256,7 +256,7 @@ Requires: golang(github.com/containers/image/docker/tarfile)
Requires: golang(github.com/containers/image/image)
Requires: golang(github.com/containers/image/oci/archive)
Requires: golang(github.com/containers/image/pkg/strslice)
-Requires: golang(github.com/containers/image/pkg/sysregistries)
+Requires: golang(github.com/containers/image/pkg/sysregistriesv2)
Requires: golang(github.com/containers/image/signature)
Requires: golang(github.com/containers/image/storage)
Requires: golang(github.com/containers/image/tarball)
diff --git a/docs/podman-generate-systemd.1.md b/docs/podman-generate-systemd.1.md
new file mode 100644
index 000000000..cc3f098a6
--- /dev/null
+++ b/docs/podman-generate-systemd.1.md
@@ -0,0 +1,69 @@
+% podman-generate Podman Man Pages
+% Brent Baude
+% April 2019
+# NAME
+podman-generate-systemd- Generate Systemd Unit file
+
+# SYNOPSIS
+**podman generate systemd** [*-n*|*--name*] [*-t*|*--timeout*] [*--restart-policy*] *container*
+
+# DESCRIPTION
+**podman generate systemd** will create a Systemd unit file that can be used to control a container. The
+command will dynamically create the unit file and output it to stdout where it can be piped by the user
+to a file. The options can be used to influence the results of the output as well.
+
+
+# OPTIONS:
+
+**--name** **-n**
+
+Use the name of the container for the start, stop, and description in the unit file
+
+**--timeout** **-t**
+
+Override the default stop timeout for the container with the given value.
+
+**--restart-policy**
+Set the SystemD restart policy. The restart-policy must be one of: "no", "on-success", "on-failure", "on-abnormal",
+"on-watchdog", "on-abort", or "always". The default policy is *on-failure*.
+
+## Examples ##
+
+Create a systemd unit file for a container running nginx:
+
+```
+$ sudo podman generate systemd nginx
+[Unit]
+Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container
+[Service]
+Restart=on-failure
+ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+ExecStop=/usr/bin/podman stop -t 10 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+KillMode=none
+Type=forking
+PIDFile=/var/lib/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc.pid
+[Install]
+WantedBy=multi-user.target
+```
+
+Create a systemd unit file for a container running nginx with an *always* restart policy and 1-second timeout.
+```
+$ sudo podman generate systemd --restart-policy=always -t 1 nginx
+[Unit]
+Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container
+[Service]
+Restart=always
+ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+ExecStop=/usr/bin/podman stop -t 1 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+KillMode=none
+Type=forking
+PIDFile=/var/lib/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc.pid
+[Install]
+WantedBy=multi-user.target
+```
+
+## SEE ALSO
+podman(1), podman-container(1)
+
+# HISTORY
+April 2019, Originally compiled by Brent Baude (bbaude at redhat dot com)
diff --git a/docs/podman-generate.1.md b/docs/podman-generate.1.md
index d1736f38e..5a2386778 100644
--- a/docs/podman-generate.1.md
+++ b/docs/podman-generate.1.md
@@ -14,6 +14,7 @@ The generate command will create structured output (like YAML) based on a contai
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| kube | [podman-generate-kube(1)](podman-generate-kube.1.md)| Generate Kubernetes YAML based on a pod or container. |
+| systemd | [podman-generate-systemd(1)](podman-generate-systemd.1.md)| Generate a systemd unit file for a container. |
## SEE ALSO
podman, podman-pod, podman-container
diff --git a/docs/podman-info.1.md b/docs/podman-info.1.md
index c606a4422..edd0252f6 100644
--- a/docs/podman-info.1.md
+++ b/docs/podman-info.1.md
@@ -52,14 +52,14 @@ host:
kernel: 4.18.7-200.fc28.x86_64
os: linux
uptime: 218h 49m 33.66s (Approximately 9.08 days)
-insecure registries:
- registries: []
registries:
- registries:
+ blocked: null
+ insecure: null
+ search:
- quay.io
- registry.fedoraproject.org
- docker.io
- - registry.access.redhat.com
+ - registry.redhat.io
store:
ConfigFile: /etc/containers/storage.conf
ContainerStore:
diff --git a/docs/podman-pod-top.1.md b/docs/podman-pod-top.1.md
index b235a70ad..fbab6bc09 100644
--- a/docs/podman-pod-top.1.md
+++ b/docs/podman-pod-top.1.md
@@ -7,7 +7,7 @@ podman\-pod\-top - Display the running processes of containers in a pod
**podman pod top** [*options*] *pod* [*format-descriptors*]
## DESCRIPTION
-Display the running process of containers in a pod. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process.
+Display the running processes of containers in a pod. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. The descriptors can either be passed as separated arguments or as a single comma-separated argument. Note that you can also specify options and or flags of ps(1); in this case, Podman will fallback to executing ps with the specified arguments and flags in the container.
## OPTIONS
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index 5190e5d14..9efb7f51c 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -593,7 +593,7 @@ Not implemented.
Restart should be handled via a systemd unit files. Please add your podman
commands to a unit file and allow systemd or your init system to handle the
-restarting of the container processes. See example below.
+restarting of the container processes. See *podman generate systemd*.
**--rm**=*true*|*false*
@@ -1151,21 +1151,6 @@ the uids and gids from the host.
$ podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello
```
-### Running a podman container to restart inside of a systemd unit file
-
-
-```
-[Unit]
-Description=My App
-[Service]
-Restart=always
-ExecStart=/usr/bin/podman start -a my_app
-ExecStop=/usr/bin/podman stop -t 10 my_app
-KillMode=process
-[Install]
-WantedBy=multi-user.target
-```
-
### Configuring Storage Options from the command line
Podman allows for the configuration of storage by changing the values
diff --git a/docs/podman-top.1.md b/docs/podman-top.1.md
index 52d1238ef..74175b753 100644
--- a/docs/podman-top.1.md
+++ b/docs/podman-top.1.md
@@ -7,7 +7,7 @@ podman\-top - Display the running processes of a container
**podman top** [*options*] *container* [*format-descriptors*]
## DESCRIPTION
-Display the running process of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process.
+Display the running processes of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. The descriptors can either be passed as separated arguments or as a single comma-separated argument. Note that you can also specify options and or flags of ps(1); in this case, Podman will fallback to executing ps with the specified arguments and flags in the container.
## OPTIONS
@@ -83,12 +83,20 @@ root 8 1 0.000 11.386886562s pts/0 0s vi
The output can be controlled by specifying format descriptors as arguments after the container:
```
-$ sudo ./bin/podman top -l pid seccomp args %C
+$ podman top -l pid seccomp args %C
PID SECCOMP COMMAND %CPU
1 filter sh 0.000
8 filter vi /etc/ 0.000
```
+Podman will fallback to executing ps(1) in the container if an unknown descriptor is specified.
+
+```
+$ podman top -l -- aux
+USER PID PPID %CPU ELAPSED TTY TIME COMMAND
+root 1 0 0.000 1h2m12.497061672s ? 0s sleep 100000
+```
+
## SEE ALSO
podman(1), ps(1), seccomp(2), proc(5), capabilities(7)
diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go
index b370495fe..392a7029e 100644
--- a/libpod/container_top_linux.go
+++ b/libpod/container_top_linux.go
@@ -20,14 +20,24 @@ func (c *Container) Top(descriptors []string) ([]string, error) {
if conStat != ContainerStateRunning {
return nil, errors.Errorf("top can only be used on running containers")
}
- return c.GetContainerPidInformation(descriptors)
+
+ // Also support comma-separated input.
+ psgoDescriptors := []string{}
+ for _, d := range descriptors {
+ for _, s := range strings.Split(d, ",") {
+ if s != "" {
+ psgoDescriptors = append(psgoDescriptors, s)
+ }
+ }
+ }
+ return c.GetContainerPidInformation(psgoDescriptors)
}
// GetContainerPidInformation returns process-related data of all processes in
// the container. The output data can be controlled via the `descriptors`
// argument which expects format descriptors and supports all AIXformat
// descriptors of ps (1) plus some additional ones to for instance inspect the
-// set of effective capabilities. Eeach element in the returned string slice
+// set of effective capabilities. Each element in the returned string slice
// is a tab-separated string.
//
// For more details, please refer to github.com/containers/psgo.
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 5a0706b07..cb7411ce5 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -13,7 +13,6 @@ import (
dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/docker/tarfile"
ociarchive "github.com/containers/image/oci/archive"
- "github.com/containers/image/pkg/sysregistries"
is "github.com/containers/image/storage"
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
@@ -284,9 +283,8 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
}
// If no image was found, we should handle. Lets be nicer to the user and see if we can figure out why.
if len(images) == 0 {
- registryPath := sysregistries.RegistriesConfPath(&types.SystemContext{SystemRegistriesConfPath: systemRegistriesConfPath})
if goal.usedSearchRegistries && len(goal.searchedRegistries) == 0 {
- return nil, errors.Errorf("image name provided is a short name and no search registries are defined in %s.", registryPath)
+ return nil, errors.Errorf("image name provided is a short name and no search registries are defined in the registries config file.")
}
// If the image passed in was fully-qualified, we will have 1 refpair. Bc the image is fq'd, we dont need to yap about registries.
if !goal.usedSearchRegistries {
diff --git a/libpod/options.go b/libpod/options.go
index 9932d5453..86c04db09 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1,7 +1,6 @@
package libpod
import (
- "context"
"net"
"os"
"path/filepath"
@@ -11,6 +10,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
@@ -437,10 +437,9 @@ func WithRenumber() RuntimeOption {
}
}
-// WithMigrate instructs libpod to perform a lock migrateing while
-// initializing. This will handle migrations from early versions of libpod with
-// file locks to newer versions with SHM locking, as well as changes in the
-// number of configured locks.
+// WithMigrate instructs libpod to migrate container configurations to account
+// for changes between Libpod versions. All running containers will be stopped
+// during a migration, then restarted after the migration is complete.
func WithMigrate() RuntimeOption {
return func(rt *Runtime) error {
if rt.valid {
@@ -467,19 +466,6 @@ func WithShmDir(dir string) CtrCreateOption {
}
}
-// WithContext sets the context to use.
-func WithContext(ctx context.Context) RuntimeOption {
- return func(rt *Runtime) error {
- if rt.valid {
- return ErrRuntimeFinalized
- }
-
- rt.ctx = ctx
-
- return nil
- }
-}
-
// WithSystemd turns on systemd mode in the container
func WithSystemd() CtrCreateOption {
return func(ctr *Container) error {
@@ -1288,7 +1274,7 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{
Name: vol.Name,
Dest: vol.Dest,
- Options: vol.Options,
+ Options: util.ProcessOptions(vol.Options),
})
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index e85242028..34b6ac74f 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -112,8 +112,6 @@ type Runtime struct {
// mechanism to read and write even logs
eventer events.Eventer
-
- ctx context.Context
}
// OCIRuntimePath contains information about an OCI runtime.
@@ -353,8 +351,8 @@ func SetXdgRuntimeDir(val string) error {
// NewRuntime creates a new container runtime
// Options can be passed to override the default configuration for the runtime
-func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
- return newRuntimeFromConfig("", options...)
+func NewRuntime(ctx context.Context, options ...RuntimeOption) (runtime *Runtime, err error) {
+ return newRuntimeFromConfig(ctx, "", options...)
}
// NewRuntimeFromConfig creates a new container runtime using the given
@@ -362,14 +360,14 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
// functions can be used to mutate this configuration further.
// An error will be returned if the configuration file at the given path does
// not exist or cannot be loaded
-func NewRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
+func NewRuntimeFromConfig(ctx context.Context, userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
if userConfigPath == "" {
return nil, errors.New("invalid configuration file specified")
}
- return newRuntimeFromConfig(userConfigPath, options...)
+ return newRuntimeFromConfig(ctx, userConfigPath, options...)
}
-func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
+func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
runtime = new(Runtime)
runtime.config = new(RuntimeConfig)
runtime.configuredFrom = new(runtimeConfiguredFrom)
@@ -563,7 +561,7 @@ func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runt
}
}
}
- if err := makeRuntime(runtime); err != nil {
+ if err := makeRuntime(ctx, runtime); err != nil {
return nil, err
}
return runtime, nil
@@ -571,7 +569,7 @@ func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runt
// Make a new runtime based on the given configuration
// Sets up containers/storage, state store, OCI runtime
-func makeRuntime(runtime *Runtime) (err error) {
+func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
// Backward compatibility for `runtime_path`
if runtime.config.RuntimePath != nil {
// Don't print twice in rootless mode.
@@ -980,7 +978,7 @@ func makeRuntime(runtime *Runtime) (err error) {
os.Exit(ret)
}
}
- if err := runtime.migrate(); err != nil {
+ if err := runtime.migrate(ctx); err != nil {
return err
}
}
@@ -1124,16 +1122,20 @@ func (r *Runtime) Info() ([]InfoData, error) {
return nil, errors.Wrapf(err, "error getting registries")
}
registries := make(map[string]interface{})
- registries["registries"] = reg
- info = append(info, InfoData{Type: "registries", Data: registries})
+ registries["search"] = reg
- i, err := sysreg.GetInsecureRegistries()
+ ireg, err := sysreg.GetInsecureRegistries()
if err != nil {
return nil, errors.Wrapf(err, "error getting registries")
}
- insecureRegistries := make(map[string]interface{})
- insecureRegistries["registries"] = i
- info = append(info, InfoData{Type: "insecure registries", Data: insecureRegistries})
+ registries["insecure"] = ireg
+
+ breg, err := sysreg.GetBlockedRegistries()
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting registries")
+ }
+ registries["blocked"] = breg
+ info = append(info, InfoData{Type: "registries", Data: registries})
return info, nil
}
diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go
index a084df289..0bb8e952f 100644
--- a/libpod/runtime_migrate.go
+++ b/libpod/runtime_migrate.go
@@ -1,13 +1,14 @@
package libpod
import (
+ "context"
"path/filepath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-func (r *Runtime) migrate() error {
+func (r *Runtime) migrate(ctx context.Context) error {
runningContainers, err := r.GetRunningContainers()
if err != nil {
return err
@@ -38,7 +39,7 @@ func (r *Runtime) migrate() error {
}
for _, ctr := range runningContainers {
- if err := ctr.Start(r.ctx, true); err != nil {
+ if err := ctr.Start(ctx, true); err != nil {
logrus.Errorf("error restarting container %s", ctr.ID())
}
}
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index eb90ab50e..0721af773 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -3,6 +3,7 @@
package adapter
import (
+ "bufio"
"context"
"fmt"
"io/ioutil"
@@ -18,6 +19,8 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter/shortcuts"
+ "github.com/containers/libpod/pkg/systemdgen"
+ "github.com/containers/psgo"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -821,7 +824,69 @@ func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) {
if err != nil {
return nil, errors.Wrapf(err, "unable to lookup requested container")
}
- return container.Top(descriptors)
+
+ output, psgoErr := container.Top(descriptors)
+ if psgoErr == nil {
+ return output, nil
+ }
+
+ // If we encountered an ErrUnknownDescriptor error, fallback to executing
+ // ps(1). This ensures backwards compatibility to users depending on ps(1)
+ // and makes sure we're ~compatible with docker.
+ if errors.Cause(psgoErr) != psgo.ErrUnknownDescriptor {
+ return nil, psgoErr
+ }
+
+ output, err = r.execPS(container, descriptors)
+ if err != nil {
+ // Note: return psgoErr to guide users into using the AIX descriptors
+ // instead of using ps(1).
+ return nil, psgoErr
+ }
+
+ // Trick: filter the ps command from the output instead of
+ // checking/requiring PIDs in the output.
+ filtered := []string{}
+ cmd := strings.Join(descriptors, " ")
+ for _, line := range output {
+ if !strings.Contains(line, cmd) {
+ filtered = append(filtered, line)
+ }
+ }
+
+ return filtered, nil
+}
+
+func (r *LocalRuntime) execPS(c *libpod.Container, args []string) ([]string, error) {
+ rPipe, wPipe, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ defer wPipe.Close()
+ defer rPipe.Close()
+
+ streams := new(libpod.AttachStreams)
+ streams.OutputStream = wPipe
+ streams.ErrorStream = wPipe
+ streams.InputStream = os.Stdin
+ streams.AttachOutput = true
+ streams.AttachError = true
+ streams.AttachInput = true
+
+ psOutput := []string{}
+ go func() {
+ scanner := bufio.NewScanner(rPipe)
+ for scanner.Scan() {
+ psOutput = append(psOutput, scanner.Text())
+ }
+ }()
+
+ cmd := append([]string{"ps"}, args...)
+ if err := c.Exec(false, false, []string{}, cmd, "", "", streams, 0); err != nil {
+ return nil, err
+ }
+
+ return psOutput, nil
}
// Prune removes stopped containers
@@ -940,3 +1005,20 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
}
return portContainers, nil
}
+
+// GenerateSystemd creates a unit file for a container
+func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
+ ctr, err := r.Runtime.LookupContainer(c.InputArgs[0])
+ if err != nil {
+ return "", err
+ }
+ timeout := int(ctr.StopTimeout())
+ if c.StopTimeout >= 0 {
+ timeout = int(c.StopTimeout)
+ }
+ name := ctr.ID()
+ if c.Name {
+ name = ctr.Name()
+ }
+ return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, ctr.Config().StaticDir, timeout)
+}
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go
index b7e353f71..201249fc3 100644
--- a/pkg/adapter/containers_remote.go
+++ b/pkg/adapter/containers_remote.go
@@ -956,3 +956,8 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
}
return containers, nil
}
+
+// GenerateSystemd creates a systemd until for a container
+func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
+ return iopodman.GenerateSystemd().Call(r.Conn, c.InputArgs[0], c.RestartPolicy, int64(c.StopTimeout), c.Name)
+}
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 9f4c94533..fdb7f33c5 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -5,7 +5,7 @@ import (
"path/filepath"
"strings"
- "github.com/containers/image/pkg/sysregistries"
+ "github.com/containers/image/pkg/sysregistriesv2"
"github.com/containers/image/types"
"github.com/containers/libpod/pkg/rootless"
"github.com/docker/distribution/reference"
@@ -34,22 +34,57 @@ func SystemRegistriesConfPath() string {
return ""
}
-// GetRegistries obtains the list of registries defined in the global registries file.
-func GetRegistries() ([]string, error) {
- searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
+func getRegistries() ([]sysregistriesv2.Registry, error) {
+ registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
if err != nil {
return nil, errors.Wrapf(err, "unable to parse the registries.conf file")
}
+ return registries, nil
+}
+
+// GetRegistries obtains the list of search registries defined in the global registries file.
+func GetRegistries() ([]string, error) {
+ var searchRegistries []string
+ registries, err := getRegistries()
+ if err != nil {
+ return nil, err
+ }
+ for _, reg := range registries {
+ if reg.Search {
+ searchRegistries = append(searchRegistries, reg.URL)
+ }
+ }
return searchRegistries, nil
}
+// GetBlockedRegistries obtains the list of blocked registries defined in the global registries file.
+func GetBlockedRegistries() ([]string, error) {
+ var blockedRegistries []string
+ registries, err := getRegistries()
+ if err != nil {
+ return nil, err
+ }
+ for _, reg := range registries {
+ if reg.Blocked {
+ blockedRegistries = append(blockedRegistries, reg.URL)
+ }
+ }
+ return blockedRegistries, nil
+}
+
// GetInsecureRegistries obtains the list of insecure registries from the global registration file.
func GetInsecureRegistries() ([]string, error) {
- registries, err := sysregistries.GetInsecureRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
+ var insecureRegistries []string
+ registries, err := getRegistries()
if err != nil {
- return nil, errors.Wrapf(err, "unable to parse the registries.conf file")
+ return nil, err
}
- return registries, nil
+ for _, reg := range registries {
+ if reg.Insecure {
+ insecureRegistries = append(insecureRegistries, reg.URL)
+ }
+ }
+ return insecureRegistries, nil
}
// GetRegistry returns the registry name from a string if specified
diff --git a/pkg/spec/containerconfig.go b/pkg/spec/containerconfig.go
new file mode 100644
index 000000000..b2f8a268f
--- /dev/null
+++ b/pkg/spec/containerconfig.go
@@ -0,0 +1,40 @@
+package createconfig
+
+import (
+ "github.com/containers/libpod/libpod"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// MakeContainerConfig generates all configuration necessary to start a
+// container with libpod from a completed CreateConfig struct.
+func (config *CreateConfig) MakeContainerConfig(runtime *libpod.Runtime, pod *libpod.Pod) (*spec.Spec, []libpod.CtrCreateOption, error) {
+ if config.Pod != "" && pod == nil {
+ return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was specified but no pod passed")
+ } else if config.Pod == "" && pod != nil {
+ return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was given but no pod is specified")
+ }
+
+ // Parse volumes flag into OCI spec mounts and libpod Named Volumes.
+ // If there is an identical mount in the OCI spec, we will replace it
+ // with a mount generated here.
+ mounts, namedVolumes, err := config.parseVolumes(runtime)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ runtimeSpec, err := config.createConfigToOCISpec(runtime, mounts)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ options, err := config.getContainerCreateOptions(runtime, pod, mounts, namedVolumes)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ logrus.Debugf("created OCI spec and options for new container")
+
+ return runtimeSpec, options, nil
+}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 505d87f09..90e7accf3 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -1,7 +1,6 @@
package createconfig
import (
- "fmt"
"net"
"os"
"strconv"
@@ -12,7 +11,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
- "github.com/containers/storage/pkg/stringid"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -25,12 +23,6 @@ import (
const (
bps = iota
iops
- // TypeBind is the type for mounting host dir
- TypeBind = "bind"
- // TypeVolume is the type for remote storage volumes
- // TypeVolume = "volume" // re-enable upon use
- // TypeTmpfs is the type for mounting tmpfs
- TypeTmpfs = "tmpfs"
)
// CreateResourceConfig represents resource elements in CreateConfig
@@ -64,7 +56,6 @@ type CreateResourceConfig struct {
// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI
type CreateConfig struct {
- Runtime *libpod.Runtime
Annotations map[string]string
Args []string
CapAdd []string // cap-add
@@ -87,6 +78,8 @@ type CreateConfig struct {
HostAdd []string //add-host
Hostname string //hostname
HTTPProxy bool
+ Init bool // init
+ InitPath string //init-path
Image string
ImageID string
BuiltinImgVolumes map[string]struct{} // volumes defined in the image config
@@ -125,15 +118,16 @@ type CreateConfig struct {
UsernsMode namespaces.UsernsMode //userns
User string //user
UtsMode namespaces.UTSMode //uts
- Mounts []spec.Mount //mounts
- Volumes []string //volume
+ Mounts []spec.Mount
+ MountsFlag []string // mounts
+ NamedVolumes []*libpod.ContainerNamedVolume
+ Volumes []string //volume
VolumesFrom []string
- NamedVolumes []*libpod.ContainerNamedVolume // Filled in by CreateConfigToOCISpec
- WorkDir string //workdir
- LabelOpts []string //SecurityOpts
- NoNewPrivs bool //SecurityOpts
- ApparmorProfile string //SecurityOpts
- SeccompProfilePath string //SecurityOpts
+ WorkDir string //workdir
+ LabelOpts []string //SecurityOpts
+ NoNewPrivs bool //SecurityOpts
+ ApparmorProfile string //SecurityOpts
+ SeccompProfilePath string //SecurityOpts
SecurityOpts []string
Rootfs string
Syslog bool // Whether to enable syslog on exit commands
@@ -147,224 +141,8 @@ func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) {
return c.createBlockIO()
}
-// AddContainerInitBinary adds the init binary specified by path iff the
-// container will run in a private PID namespace that is not shared with the
-// host or another pre-existing container, where an init-like process is
-// already running.
-//
-// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
-// to execute the bind-mounted binary as PID 1.
-func (c *CreateConfig) AddContainerInitBinary(path string) error {
- if path == "" {
- return fmt.Errorf("please specify a path to the container-init binary")
- }
- if !c.PidMode.IsPrivate() {
- return fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
- }
- if c.Systemd {
- return fmt.Errorf("cannot use container-init binary with systemd")
- }
- if _, err := os.Stat(path); os.IsNotExist(err) {
- return errors.Wrap(err, "container-init binary not found on the host")
- }
- c.Command = append([]string{"/dev/init", "--"}, c.Command...)
- c.Mounts = append(c.Mounts, spec.Mount{
- Destination: "/dev/init",
- Type: TypeBind,
- Source: path,
- Options: []string{TypeBind, "ro"},
- })
- return nil
-}
-
-func processOptions(options []string) []string {
- var (
- foundrw, foundro bool
- rootProp string
- )
- options = append(options, "rbind")
- for _, opt := range options {
- switch opt {
- case "rw":
- foundrw = true
- case "ro":
- foundro = true
- case "private", "rprivate", "slave", "rslave", "shared", "rshared":
- rootProp = opt
- }
- }
- if !foundrw && !foundro {
- options = append(options, "rw")
- }
- if rootProp == "" {
- options = append(options, "rprivate")
- }
- return options
-}
-
-func (c *CreateConfig) initFSMounts() []spec.Mount {
- var mounts []spec.Mount
- for _, m := range c.Mounts {
- m.Options = processOptions(m.Options)
- if m.Type == "tmpfs" {
- m.Options = append(m.Options, "tmpcopyup")
- } else {
- mounts = append(mounts, m)
- }
- }
- return mounts
-}
-
-// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
-func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) {
- m := []spec.Mount{}
- for _, i := range c.Volumes {
- var options []string
- spliti := strings.Split(i, ":")
- if len(spliti) > 2 {
- options = strings.Split(spliti[2], ",")
- }
-
- m = append(m, spec.Mount{
- Destination: spliti[1],
- Type: string(TypeBind),
- Source: spliti[0],
- Options: processOptions(options),
- })
-
- logrus.Debugf("User mount %s:%s options %v", spliti[0], spliti[1], options)
- }
-
- if c.ImageVolumeType == "ignore" {
- return m, nil
- }
-
- for vol := range c.BuiltinImgVolumes {
- if libpod.MountExists(specMounts, vol) || libpod.MountExists(m, vol) {
- continue
- }
-
- mount := spec.Mount{
- Destination: vol,
- Type: c.ImageVolumeType,
- Options: []string{"rprivate", "rw", "nodev"},
- }
- if c.ImageVolumeType == "tmpfs" {
- mount.Source = "tmpfs"
- mount.Options = append(mount.Options, "tmpcopyup")
- } else {
- // TODO: Move support for this and tmpfs into libpod
- // Should tmpfs also be handled as named volumes? Wouldn't be hard
- // This will cause a new local Volume to be created on your system
- mount.Source = stringid.GenerateNonCryptoID()
- mount.Options = append(mount.Options, TypeBind)
- }
- m = append(m, mount)
- }
-
- return m, nil
-}
-
-// GetVolumesFrom reads the create-config artifact of the container to get volumes from
-// and adds it to c.Volumes of the current container.
-func (c *CreateConfig) GetVolumesFrom() error {
- if os.Geteuid() != 0 {
- return nil
- }
-
- for _, vol := range c.VolumesFrom {
- options := ""
- splitVol := strings.SplitN(vol, ":", 2)
- if len(splitVol) == 2 {
- options = splitVol[1]
- }
- ctr, err := c.Runtime.LookupContainer(splitVol[0])
- if err != nil {
- return errors.Wrapf(err, "error looking up container %q", splitVol[0])
- }
-
- logrus.Debugf("Adding volumes from container %s", ctr.ID())
-
- // Look up the container's user volumes. This gets us the
- // destinations of all mounts the user added to the container.
- userVolumesArr := ctr.UserVolumes()
-
- // We're going to need to access them a lot, so convert to a map
- // to reduce looping.
- // We'll also use the map to indicate if we missed any volumes along the way.
- userVolumes := make(map[string]bool)
- for _, dest := range userVolumesArr {
- userVolumes[dest] = false
- }
-
- // Now we get the container's spec and loop through its volumes
- // and append them in if we can find them.
- spec := ctr.Spec()
- if spec == nil {
- return errors.Errorf("error retrieving container %s spec", ctr.ID())
- }
- for _, mnt := range spec.Mounts {
- if mnt.Type != TypeBind {
- continue
- }
- if _, exists := userVolumes[mnt.Destination]; exists {
- userVolumes[mnt.Destination] = true
- localOptions := options
- if localOptions == "" {
- localOptions = strings.Join(mnt.Options, ",")
- }
- c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", mnt.Source, mnt.Destination, localOptions))
- }
- }
-
- // We're done with the spec mounts. Add named volumes.
- // Add these unconditionally - none of them are automatically
- // part of the container, as some spec mounts are.
- namedVolumes := ctr.NamedVolumes()
- for _, namedVol := range namedVolumes {
- if _, exists := userVolumes[namedVol.Dest]; exists {
- userVolumes[namedVol.Dest] = true
- }
- localOptions := options
- if localOptions == "" {
- localOptions = strings.Join(namedVol.Options, ",")
- }
- c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, localOptions))
- }
-
- // Check if we missed any volumes
- for volDest, found := range userVolumes {
- if !found {
- logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
- }
- }
- }
- return nil
-}
-
-//GetTmpfsMounts takes user provided input for Tmpfs mounts and creates Mount structs
-func (c *CreateConfig) GetTmpfsMounts() []spec.Mount {
- var m []spec.Mount
- for _, i := range c.Tmpfs {
- // Default options if nothing passed
- options := []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "size=65536k"}
- spliti := strings.Split(i, ":")
- destPath := spliti[0]
- if len(spliti) > 1 {
- options = strings.Split(spliti[1], ",")
- }
- m = append(m, spec.Mount{
- Destination: destPath,
- Type: string(TypeTmpfs),
- Options: options,
- Source: string(TypeTmpfs),
- })
- }
- return m
-}
-
-func (c *CreateConfig) createExitCommand() ([]string, error) {
- config, err := c.Runtime.GetConfig()
+func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, error) {
+ config, err := runtime.GetConfig()
if err != nil {
return nil, err
}
@@ -396,7 +174,7 @@ func (c *CreateConfig) createExitCommand() ([]string, error) {
}
// GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions
-func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
+func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod, mounts []spec.Mount, namedVolumes []*libpod.ContainerNamedVolume) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var portBindings []ocicni.PortMapping
var err error
@@ -409,16 +187,10 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
options = append(options, libpod.WithSystemd())
}
if c.Name != "" {
- logrus.Debugf("appending name %s", c.Name)
+ logrus.Debugf("setting container name %s", c.Name)
options = append(options, libpod.WithName(c.Name))
}
- if c.Pod != "" || pod != nil {
- if pod == nil {
- pod, err = runtime.LookupPod(c.Pod)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to add container to pod %s", c.Pod)
- }
- }
+ if c.Pod != "" {
logrus.Debugf("adding container to pod %s", c.Pod)
options = append(options, runtime.WithPod(pod))
}
@@ -429,26 +201,22 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
}
- if len(c.Volumes) != 0 {
- // Volumes consist of multiple, comma-delineated fields
- // The image spec only includes one part of that, so drop the
- // others, if they are included
- volumes := make([]string, 0, len(c.Volumes))
- for _, vol := range c.Volumes {
- // We always want the volume destination
- splitVol := strings.SplitN(vol, ":", 3)
- if len(splitVol) > 1 {
- volumes = append(volumes, splitVol[1])
- } else {
- volumes = append(volumes, splitVol[0])
- }
+ if len(mounts) != 0 || len(namedVolumes) != 0 {
+ destinations := []string{}
+
+ // Take all mount and named volume destinations.
+ for _, mount := range mounts {
+ destinations = append(destinations, mount.Destination)
+ }
+ for _, volume := range namedVolumes {
+ destinations = append(destinations, volume.Dest)
}
- options = append(options, libpod.WithUserVolumes(volumes))
+ options = append(options, libpod.WithUserVolumes(destinations))
}
- if len(c.NamedVolumes) != 0 {
- options = append(options, libpod.WithNamedVolumes(c.NamedVolumes))
+ if len(namedVolumes) != 0 {
+ options = append(options, libpod.WithNamedVolumes(namedVolumes))
}
if len(c.Command) != 0 {
@@ -484,7 +252,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
return nil, err
}
} else if c.NetMode.IsContainer() {
- connectedCtr, err := c.Runtime.LookupContainer(c.NetMode.Container())
+ connectedCtr, err := runtime.LookupContainer(c.NetMode.Container())
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", c.NetMode.Container())
}
@@ -495,7 +263,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
if c.PidMode.IsContainer() {
- connectedCtr, err := c.Runtime.LookupContainer(c.PidMode.Container())
+ connectedCtr, err := runtime.LookupContainer(c.PidMode.Container())
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container())
}
@@ -504,7 +272,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
if c.IpcMode.IsContainer() {
- connectedCtr, err := c.Runtime.LookupContainer(c.IpcMode.Container())
+ connectedCtr, err := runtime.LookupContainer(c.IpcMode.Container())
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container())
}
@@ -516,7 +284,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
options = append(options, libpod.WithUTSNSFromPod(pod))
}
if c.UtsMode.IsContainer() {
- connectedCtr, err := c.Runtime.LookupContainer(c.UtsMode.Container())
+ connectedCtr, err := runtime.LookupContainer(c.UtsMode.Container())
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", c.UtsMode.Container())
}
@@ -592,7 +360,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
// Always use a cleanup process to clean up Podman after termination
- exitCmd, err := c.createExitCommand()
+ exitCmd, err := c.createExitCommand(runtime)
if err != nil {
return nil, err
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 383eeadf3..20c649f9a 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -2,13 +2,11 @@ package createconfig
import (
"os"
- "path"
"path/filepath"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/storage/pkg/mount"
pmount "github.com/containers/storage/pkg/mount"
"github.com/docker/docker/oci/caps"
"github.com/docker/go-units"
@@ -21,61 +19,6 @@ import (
const cpuPeriod = 100000
-func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
- if len(mounts) > 0 {
- // If we have overlappings mounts, remove them from the spec in favor of
- // the user-added volume mounts
- destinations := make(map[string]bool)
- for _, mount := range mounts {
- destinations[path.Clean(mount.Destination)] = true
- }
- // Copy all mounts from spec to defaultMounts, except for
- // - mounts overridden by a user supplied mount;
- // - all mounts under /dev if a user supplied /dev is present;
- mountDev := destinations["/dev"]
- for _, mount := range configMount {
- if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
- if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
- // filter out everything under /dev if /dev is user-mounted
- continue
- }
-
- logrus.Debugf("Adding mount %s", mount.Destination)
- mounts = append(mounts, mount)
- }
- }
- return mounts
- }
- return configMount
-}
-
-// Split named volumes from normal volumes
-func splitNamedVolumes(mounts []spec.Mount) ([]spec.Mount, []*libpod.ContainerNamedVolume) {
- newMounts := make([]spec.Mount, 0)
- namedVolumes := make([]*libpod.ContainerNamedVolume, 0)
- for _, mount := range mounts {
- // If it's not a named volume, append unconditionally
- if mount.Type != TypeBind {
- newMounts = append(newMounts, mount)
- continue
- }
- // Volumes that are not named volumes must be an absolute or
- // relative path.
- // Volume names may not begin with a non-alphanumeric character
- // so the HasPrefix() check is safe here.
- if strings.HasPrefix(mount.Source, "/") || strings.HasPrefix(mount.Source, ".") {
- newMounts = append(newMounts, mount)
- } else {
- namedVolume := new(libpod.ContainerNamedVolume)
- namedVolume.Name = mount.Source
- namedVolume.Dest = mount.Destination
- namedVolume.Options = mount.Options
- namedVolumes = append(namedVolumes, namedVolume)
- }
- }
- return newMounts, namedVolumes
-}
-
func getAvailableGids() (int64, error) {
idMap, err := user.ParseIDMapFile("/proc/self/gid_map")
if err != nil {
@@ -89,7 +32,7 @@ func getAvailableGids() (int64, error) {
}
// CreateConfigToOCISpec parses information needed to create a container into an OCI runtime spec
-func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
+func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userMounts []spec.Mount) (*spec.Spec, error) {
cgroupPerm := "ro"
g, err := generate.New("linux")
if err != nil {
@@ -334,56 +277,6 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
addedResources = true
}
- for _, i := range config.Tmpfs {
- // Default options if nothing passed
- options := []string{"rw", "rprivate", "noexec", "nosuid", "nodev", "size=65536k"}
- spliti := strings.SplitN(i, ":", 2)
- if len(spliti) > 1 {
- if _, _, err := mount.ParseTmpfsOptions(spliti[1]); err != nil {
- return nil, err
- }
- options = strings.Split(spliti[1], ",")
- }
- tmpfsMnt := spec.Mount{
- Destination: spliti[0],
- Type: "tmpfs",
- Source: "tmpfs",
- Options: append(options, "tmpcopyup"),
- }
- g.AddMount(tmpfsMnt)
- }
-
- for _, m := range config.Mounts {
- if m.Type == "tmpfs" {
- g.AddMount(m)
- }
- }
-
- if config.ReadOnlyRootfs && config.ReadOnlyTmpfs {
- options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
- for _, i := range []string{"/tmp", "/var/tmp"} {
- if libpod.MountExists(g.Config.Mounts, i) {
- continue
- }
- // Default options if nothing passed
- tmpfsMnt := spec.Mount{
- Destination: i,
- Type: "tmpfs",
- Source: "tmpfs",
- Options: options,
- }
- g.AddMount(tmpfsMnt)
- }
- if !libpod.MountExists(g.Config.Mounts, "/run") {
- tmpfsMnt := spec.Mount{
- Destination: "/run",
- Type: "tmpfs",
- Source: "tmpfs",
- Options: append(options, "noexec", "size=65536k"),
- }
- g.AddMount(tmpfsMnt)
- }
- }
for name, val := range config.Env {
g.AddProcessEnv(name, val)
}
@@ -439,23 +332,9 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
}
// BIND MOUNTS
- if err := config.GetVolumesFrom(); err != nil {
- return nil, errors.Wrap(err, "error getting volume mounts from --volumes-from flag")
- }
-
- volumeMounts, err := config.GetVolumeMounts(configSpec.Mounts)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting volume mounts")
- }
-
- configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts)
- //--mount
- configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts)
-
- // Split normal mounts and named volumes
- newMounts, namedVolumes := splitNamedVolumes(configSpec.Mounts)
- configSpec.Mounts = newMounts
- config.NamedVolumes = namedVolumes
+ configSpec.Mounts = supercedeUserMounts(userMounts, configSpec.Mounts)
+ // Process mounts to ensure correct options
+ configSpec.Mounts = initFSMounts(configSpec.Mounts)
// BLOCK IO
blkio, err := config.CreateBlockIO()
diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go
index c037bf69e..0abff491b 100644
--- a/pkg/spec/spec_test.go
+++ b/pkg/spec/spec_test.go
@@ -1,39 +1,98 @@
package createconfig
import (
- "reflect"
+ "runtime"
"testing"
- spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/containers/libpod/pkg/sysinfo"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/idtools"
+ "github.com/docker/go-units"
"github.com/stretchr/testify/assert"
)
-func TestCreateConfig_GetVolumeMounts(t *testing.T) {
- data := spec.Mount{
- Destination: "/foobar",
- Type: "bind",
- Source: "foobar",
- Options: []string{"ro", "rbind", "rprivate"},
+var (
+ sysInfo = sysinfo.New(true)
+)
+
+// Make createconfig to test with
+func makeTestCreateConfig() *CreateConfig {
+ cc := new(CreateConfig)
+ cc.Resources = CreateResourceConfig{}
+ cc.IDMappings = new(storage.IDMappingOptions)
+ cc.IDMappings.UIDMap = []idtools.IDMap{}
+ cc.IDMappings.GIDMap = []idtools.IDMap{}
+
+ return cc
+}
+
+// TestPIDsLimit verifies the given pid-limit is correctly defined in the spec
+func TestPIDsLimit(t *testing.T) {
+ // The default configuration of podman enables seccomp, which is not available on non-Linux systems.
+ // Thus, any tests that use the default seccomp setting would fail.
+ // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result.
+ if runtime.GOOS != "linux" {
+ t.Skip("seccomp, which is enabled by default, is only supported on Linux")
}
- config := CreateConfig{
- Volumes: []string{"foobar:/foobar:ro"},
+ if !sysInfo.PidsLimit {
+ t.Skip("running test not supported by the host system")
}
- specMount, err := config.GetVolumeMounts([]spec.Mount{})
+
+ cc := makeTestCreateConfig()
+ cc.Resources.PidsLimit = 22
+
+ spec, err := cc.createConfigToOCISpec(nil, nil)
assert.NoError(t, err)
- assert.True(t, reflect.DeepEqual(data, specMount[0]))
+
+ assert.Equal(t, spec.Linux.Resources.Pids.Limit, int64(22))
}
-func TestCreateConfig_GetTmpfsMounts(t *testing.T) {
- data := spec.Mount{
- Destination: "/homer",
- Type: "tmpfs",
- Source: "tmpfs",
- Options: []string{"rw", "size=787448k", "mode=1777"},
+// TestBLKIOWeightDevice verifies the given blkio weight is correctly set in the
+// spec.
+func TestBLKIOWeightDevice(t *testing.T) {
+ // The default configuration of podman enables seccomp, which is not available on non-Linux systems.
+ // Thus, any tests that use the default seccomp setting would fail.
+ // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result.
+ if runtime.GOOS != "linux" {
+ t.Skip("seccomp, which is enabled by default, is only supported on Linux")
}
- config := CreateConfig{
- Tmpfs: []string{"/homer:rw,size=787448k,mode=1777"},
+ if !sysInfo.BlkioWeightDevice {
+ t.Skip("running test not supported by the host system")
}
- tmpfsMount := config.GetTmpfsMounts()
- assert.True(t, reflect.DeepEqual(data, tmpfsMount[0]))
+ cc := makeTestCreateConfig()
+ cc.Resources.BlkioWeightDevice = []string{"/dev/zero:100"}
+
+ spec, err := cc.createConfigToOCISpec(nil, nil)
+ assert.NoError(t, err)
+
+ // /dev/zero is guaranteed 1,5 by the Linux kernel
+ assert.Equal(t, spec.Linux.Resources.BlockIO.WeightDevice[0].Major, int64(1))
+ assert.Equal(t, spec.Linux.Resources.BlockIO.WeightDevice[0].Minor, int64(5))
+ assert.Equal(t, *(spec.Linux.Resources.BlockIO.WeightDevice[0].Weight), uint16(100))
+}
+
+// TestMemorySwap verifies that the given swap memory limit is correctly set in
+// the spec.
+func TestMemorySwap(t *testing.T) {
+ // The default configuration of podman enables seccomp, which is not available on non-Linux systems.
+ // Thus, any tests that use the default seccomp setting would fail.
+ // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result.
+ if runtime.GOOS != "linux" {
+ t.Skip("seccomp, which is enabled by default, is only supported on Linux")
+ }
+ if !sysInfo.SwapLimit {
+ t.Skip("running test not supported by the host system")
+ }
+
+ swapLimit, err := units.RAMInBytes("45m")
+ assert.NoError(t, err)
+
+ cc := makeTestCreateConfig()
+ cc.Resources.MemorySwap = swapLimit
+
+ spec, err := cc.createConfigToOCISpec(nil, nil)
+ assert.NoError(t, err)
+
+ assert.Equal(t, *(spec.Linux.Resources.Memory.Swap), swapLimit)
}
diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go
new file mode 100644
index 000000000..55148b606
--- /dev/null
+++ b/pkg/spec/storage.go
@@ -0,0 +1,792 @@
+package createconfig
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/containers/storage/pkg/stringid"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // TypeBind is the type for mounting host dir
+ TypeBind = "bind"
+ // TypeVolume is the type for named volumes
+ TypeVolume = "volume"
+ // TypeTmpfs is the type for mounting tmpfs
+ TypeTmpfs = "tmpfs"
+)
+
+var (
+ errDuplicateDest = errors.Errorf("duplicate mount destination")
+ optionArgError = errors.Errorf("must provide an argument for option")
+ noDestError = errors.Errorf("must set volume destination")
+)
+
+// Parse all volume-related options in the create config into a set of mounts
+// and named volumes to add to the container.
+// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags.
+// TODO: Named volume options - should we default to rprivate? It bakes into a
+// bind mount under the hood...
+// TODO: handle options parsing/processing via containers/storage/pkg/mount
+func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, []*libpod.ContainerNamedVolume, error) {
+ // Add image volumes.
+ baseMounts, baseVolumes, err := config.getImageVolumes()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Add --volumes-from.
+ // Overrides image volumes unconditionally.
+ vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime)
+ if err != nil {
+ return nil, nil, err
+ }
+ for dest, mount := range vFromMounts {
+ baseMounts[dest] = mount
+ }
+ for dest, volume := range vFromVolumes {
+ baseVolumes[dest] = volume
+ }
+
+ // Next mounts from the --mounts flag.
+ // Do not override yet.
+ unifiedMounts, unifiedVolumes, err := config.getMounts()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --volumes flag.
+ // Do not override yet.
+ volumeMounts, volumeVolumes, err := config.getVolumeMounts()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --tmpfs flag.
+ // Do not override yet.
+ tmpfsMounts, err := config.getTmpfsMounts()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Unify mounts from --mount, --volume, --tmpfs.
+ // Also add mounts + volumes directly from createconfig.
+ // Start with --volume.
+ for dest, mount := range volumeMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = mount
+ }
+ for dest, volume := range volumeVolumes {
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedVolumes[dest] = volume
+ }
+ // Now --tmpfs
+ for dest, tmpfs := range tmpfsMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = tmpfs
+ }
+ // Now spec mounts and volumes
+ for _, mount := range config.Mounts {
+ dest := mount.Destination
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = mount
+ }
+ for _, volume := range config.NamedVolumes {
+ dest := volume.Dest
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedVolumes[dest] = volume
+ }
+
+ // If requested, add container init binary
+ if config.Init {
+ initPath := config.InitPath
+ if initPath == "" {
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ return nil, nil, err
+ }
+ initPath = rtc.InitPath
+ }
+ initMount, err := config.addContainerInitBinary(initPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := unifiedMounts[initMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
+ }
+ unifiedMounts[initMount.Destination] = initMount
+ }
+
+ // If requested, add tmpfs filesystems for read-only containers.
+ // Need to keep track of which we created, so we don't modify options
+ // for them later...
+ readonlyTmpfs := map[string]bool{
+ "/tmp": false,
+ "/var/tmp": false,
+ "/run": false,
+ }
+ if config.ReadOnlyRootfs && config.ReadOnlyTmpfs {
+ options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup", "size=65536k"}
+ for dest := range readonlyTmpfs {
+ if _, ok := unifiedMounts[dest]; ok {
+ continue
+ }
+ localOpts := options
+ if dest == "/run" {
+ localOpts = append(localOpts, "noexec")
+ }
+ unifiedMounts[dest] = spec.Mount{
+ Destination: dest,
+ Type: "tmpfs",
+ Source: "tmpfs",
+ Options: localOpts,
+ }
+ readonlyTmpfs[dest] = true
+ }
+ }
+
+ // Supercede volumes-from/image volumes with unified volumes from above.
+ // This is an unconditional replacement.
+ for dest, mount := range unifiedMounts {
+ baseMounts[dest] = mount
+ }
+ for dest, volume := range unifiedVolumes {
+ baseVolumes[dest] = volume
+ }
+
+ // Check for conflicts between named volumes and mounts
+ for dest := range baseMounts {
+ if _, ok := baseVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+ for dest := range baseVolumes {
+ if _, ok := baseMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+
+ // Final step: maps to arrays
+ finalMounts := make([]spec.Mount, 0, len(baseMounts))
+ for _, mount := range baseMounts {
+ // All user-added tmpfs mounts need their options processed.
+ // Exception: mounts added by the ReadOnlyTmpfs option, which
+ // contain several exceptions to normal options rules.
+ if mount.Type == TypeTmpfs && !readonlyTmpfs[mount.Destination] {
+ opts, err := util.ProcessTmpfsOptions(mount.Options)
+ if err != nil {
+ return nil, nil, err
+ }
+ mount.Options = opts
+ }
+ finalMounts = append(finalMounts, mount)
+ }
+ finalVolumes := make([]*libpod.ContainerNamedVolume, 0, len(baseVolumes))
+ for _, volume := range baseVolumes {
+ finalVolumes = append(finalVolumes, volume)
+ }
+
+ logrus.Debugf("Got mounts: %v", finalMounts)
+ logrus.Debugf("Got volumes: %v", finalVolumes)
+
+ return finalMounts, finalVolumes, nil
+}
+
+// Parse volumes from - a set of containers whose volumes we will mount in.
+// Grab the containers, retrieve any user-created spec mounts and all named
+// volumes, and return a list of them.
+// Conflicts are resolved simply - the last container specified wins.
+// Container names may be suffixed by mount options after a colon.
+func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ // TODO: This can probably be disabled now
+ if os.Geteuid() != 0 {
+ return nil, nil, nil
+ }
+
+ // Both of these are maps of mount destination to mount type.
+ // We ensure that each destination is only mounted to once in this way.
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ for _, vol := range config.VolumesFrom {
+ options := []string{}
+ splitVol := strings.SplitN(vol, ":", 2)
+ if len(splitVol) == 2 {
+ if strings.Contains(splitVol[1], "Z") ||
+ strings.Contains(splitVol[1], "private") ||
+ strings.Contains(splitVol[1], "slave") ||
+ strings.Contains(splitVol[1], "shared") {
+ return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1])
+ }
+ options = strings.Split(splitVol[1], ",")
+ if err := ValidateVolumeOpts(options); err != nil {
+ return nil, nil, err
+ }
+ }
+ ctr, err := runtime.LookupContainer(splitVol[0])
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
+ }
+
+ logrus.Debugf("Adding volumes from container %s", ctr.ID())
+
+ // Look up the container's user volumes. This gets us the
+ // destinations of all mounts the user added to the container.
+ userVolumesArr := ctr.UserVolumes()
+
+ // We're going to need to access them a lot, so convert to a map
+ // to reduce looping.
+ // We'll also use the map to indicate if we missed any volumes along the way.
+ userVolumes := make(map[string]bool)
+ for _, dest := range userVolumesArr {
+ userVolumes[dest] = false
+ }
+
+ // Now we get the container's spec and loop through its volumes
+ // and append them in if we can find them.
+ spec := ctr.Spec()
+ if spec == nil {
+ return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
+ }
+ for _, mnt := range spec.Mounts {
+ if mnt.Type != TypeBind {
+ continue
+ }
+ if _, exists := userVolumes[mnt.Destination]; exists {
+ userVolumes[mnt.Destination] = true
+
+ if len(options) != 0 {
+ mnt.Options = options
+ }
+
+ if _, ok := finalMounts[mnt.Destination]; ok {
+ logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
+ }
+ finalMounts[mnt.Destination] = mnt
+ }
+ }
+
+ // We're done with the spec mounts. Add named volumes.
+ // Add these unconditionally - none of them are automatically
+ // part of the container, as some spec mounts are.
+ namedVolumes := ctr.NamedVolumes()
+ for _, namedVol := range namedVolumes {
+ if _, exists := userVolumes[namedVol.Dest]; exists {
+ userVolumes[namedVol.Dest] = true
+ }
+
+ if len(options) != 0 {
+ namedVol.Options = options
+ }
+
+ if _, ok := finalMounts[namedVol.Dest]; ok {
+ logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
+ }
+ finalNamedVolumes[namedVol.Dest] = namedVol
+ }
+
+ // Check if we missed any volumes
+ for volDest, found := range userVolumes {
+ if !found {
+ logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
+ }
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, nil
+}
+
+// getMounts takes user-provided input from the --mount flag and creates OCI
+// spec mounts and Libpod named volumes.
+// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// podman run --mount type=tmpfs,target=/dev/shm ...
+// podman run --mount type=volume,source=test-volume, ...
+func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+
+ // TODO(vrothberg): the manual parsing can be replaced with a regular expression
+ // to allow a more robust parsing of the mount format and to give
+ // precise errors regarding supported format versus suppored options.
+ for _, mount := range config.MountsFlag {
+ arr := strings.SplitN(mount, ",", 2)
+ if len(arr) < 2 {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+ kv := strings.Split(arr[0], "=")
+ // TODO: type is not explicitly required in Docker.
+ // If not specified, it defaults to "volume".
+ if len(kv) != 2 || kv[0] != "type" {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+
+ tokens := strings.Split(arr[1], ",")
+ switch kv[1] {
+ case TypeBind:
+ mount, err := getBindMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case TypeTmpfs:
+ mount, err := getTmpfsMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case "volume":
+ volume, err := getNamedVolume(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalNamedVolumes[volume.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
+ }
+ finalNamedVolumes[volume.Dest] = volume
+ default:
+ return nil, nil, errors.Errorf("invalid fylesystem type %q", kv[1])
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, nil
+}
+
+// Parse a single bind mount entry from the --mount flag.
+func getBindMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: TypeBind,
+ }
+
+ setSource := false
+ setDest := false
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ // TODO: detect duplication of these options.
+ // (Is this necessary?)
+ newMount.Options = append(newMount.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "bind-propagation":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, kv[1])
+ case "src", "source":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := ValidateVolumeHostDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Source = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = kv[1]
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ if !setSource {
+ newMount.Source = newMount.Destination
+ }
+
+ if err := ValidateVolumeOpts(newMount.Options); err != nil {
+ return newMount, err
+ }
+
+ return newMount, nil
+}
+
+// Parse a single tmpfs mount entry from the --mount flag
+func getTmpfsMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: TypeTmpfs,
+ Source: TypeTmpfs,
+ }
+
+ setDest := false
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "tmpfs-mode":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
+ case "tmpfs-size":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
+ case "src", "source":
+ return newMount, errors.Errorf("source is not supported with tmpfs mounts")
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = kv[1]
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single volume mount entry from the --mount flag.
+// Note that the volume-label option for named volumes is currently NOT supported.
+// TODO: add support for --volume-label
+func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) {
+ newVolume := new(libpod.ContainerNamedVolume)
+
+ setSource := false
+ setDest := false
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ // TODO: detect duplication of these options
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "volume-label":
+ return nil, errors.Errorf("the --volume-label option is not presently implemented")
+ case "src", "source":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ newVolume.Name = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Dest = kv[1]
+ setDest = true
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setSource {
+ return nil, errors.Errorf("must set source volume")
+ }
+ if !setDest {
+ return nil, noDestError
+ }
+
+ return newVolume, nil
+}
+
+// ValidateVolumeHostDir validates a volume mount's source directory
+func ValidateVolumeHostDir(hostDir string) error {
+ if len(hostDir) == 0 {
+ return errors.Errorf("host directory cannot be empty")
+ }
+ if filepath.IsAbs(hostDir) {
+ if _, err := os.Stat(hostDir); err != nil {
+ return errors.Wrapf(err, "error checking path %q", hostDir)
+ }
+ }
+ // If hostDir is not an absolute path, that means the user wants to create a
+ // named volume. This will be done later on in the code.
+ return nil
+}
+
+// ValidateVolumeCtrDir validates a volume mount's destination directory.
+func ValidateVolumeCtrDir(ctrDir string) error {
+ if len(ctrDir) == 0 {
+ return errors.Errorf("container directory cannot be empty")
+ }
+ if !filepath.IsAbs(ctrDir) {
+ return errors.Errorf("invalid container path %q, must be an absolute path", ctrDir)
+ }
+ return nil
+}
+
+// ValidateVolumeOpts validates a volume's options
+func ValidateVolumeOpts(options []string) error {
+ var foundRootPropagation, foundRWRO, foundLabelChange int
+ for _, opt := range options {
+ switch opt {
+ case "rw", "ro":
+ foundRWRO++
+ if foundRWRO > 1 {
+ return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", "))
+ }
+ case "z", "Z":
+ foundLabelChange++
+ if foundLabelChange > 1 {
+ return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", "))
+ }
+ case "private", "rprivate", "shared", "rshared", "slave", "rslave":
+ foundRootPropagation++
+ if foundRootPropagation > 1 {
+ return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", "))
+ }
+ default:
+ return errors.Errorf("invalid option type %q", opt)
+ }
+ }
+ return nil
+}
+
+// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
+func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ volumeFormatErr := errors.Errorf("incorrect volume format, should be host-dir:ctr-dir[:option]")
+
+ for _, vol := range config.Volumes {
+ var (
+ options []string
+ src string
+ dest string
+ )
+
+ splitVol := strings.Split(vol, ":")
+ if len(splitVol) > 3 {
+ return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ }
+
+ src = splitVol[0]
+ if len(splitVol) == 1 {
+ dest = src
+ } else if len(splitVol) > 1 {
+ dest = splitVol[1]
+ }
+ if len(splitVol) > 2 {
+ options = strings.Split(splitVol[2], ",")
+ if err := ValidateVolumeOpts(options); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ if err := ValidateVolumeHostDir(src); err != nil {
+ return nil, nil, err
+ }
+ if err := ValidateVolumeCtrDir(dest); err != nil {
+ return nil, nil, err
+ }
+
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ // This is not a named volume
+ newMount := spec.Mount{
+ Destination: dest,
+ Type: string(TypeBind),
+ Source: src,
+ Options: options,
+ }
+ if _, ok := mounts[newMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ }
+ mounts[newMount.Destination] = newMount
+ } else {
+ // This is a named volume
+ newNamedVol := new(libpod.ContainerNamedVolume)
+ newNamedVol.Name = src
+ newNamedVol.Dest = dest
+ newNamedVol.Options = options
+
+ if _, ok := volumes[newNamedVol.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ }
+ volumes[newNamedVol.Dest] = newNamedVol
+ }
+
+ logrus.Debugf("User mount %s:%s options %v", src, dest, options)
+ }
+
+ return mounts, volumes, nil
+}
+
+// Get mounts for container's image volumes
+func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ if config.ImageVolumeType == "ignore" {
+ return mounts, volumes, nil
+ }
+
+ for vol := range config.BuiltinImgVolumes {
+ if config.ImageVolumeType == "tmpfs" {
+ // Tmpfs image volumes are handled as mounts
+ mount := spec.Mount{
+ Destination: vol,
+ Source: TypeTmpfs,
+ Type: TypeTmpfs,
+ Options: []string{"rprivate", "rw", "nodev"},
+ }
+ mounts[vol] = mount
+ } else {
+ namedVolume := new(libpod.ContainerNamedVolume)
+ namedVolume.Name = stringid.GenerateNonCryptoID()
+ namedVolume.Options = []string{"rprivate", "rw", "nodev"}
+ namedVolume.Dest = vol
+ volumes[vol] = namedVolume
+ }
+ }
+
+ return mounts, volumes, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func (config *CreateConfig) getTmpfsMounts() (map[string]spec.Mount, error) {
+ m := make(map[string]spec.Mount)
+ for _, i := range config.Tmpfs {
+ // Default options if nothing passed
+ var options []string
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+
+ if _, ok := m[destPath]; ok {
+ return nil, errors.Wrapf(errDuplicateDest, destPath)
+ }
+
+ mount := spec.Mount{
+ Destination: destPath,
+ Type: string(TypeTmpfs),
+ Options: options,
+ Source: string(TypeTmpfs),
+ }
+ m[destPath] = mount
+ }
+ return m, nil
+}
+
+// AddContainerInitBinary adds the init binary specified by path iff the
+// container will run in a private PID namespace that is not shared with the
+// host or another pre-existing container, where an init-like process is
+// already running.
+//
+// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
+// to execute the bind-mounted binary as PID 1.
+func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, error) {
+ mount := spec.Mount{
+ Destination: "/dev/init",
+ Type: TypeBind,
+ Source: path,
+ Options: []string{TypeBind, "ro"},
+ }
+
+ if path == "" {
+ return mount, fmt.Errorf("please specify a path to the container-init binary")
+ }
+ if !config.PidMode.IsPrivate() {
+ return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
+ }
+ if config.Systemd {
+ return mount, fmt.Errorf("cannot use container-init binary with systemd")
+ }
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ return mount, errors.Wrap(err, "container-init binary not found on the host")
+ }
+ config.Command = append([]string{"/dev/init", "--"}, config.Command...)
+ return mount, nil
+}
+
+// Supercede existing mounts in the spec with new, user-specified mounts.
+// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
+// one mount, and we already have /tmp/a and /tmp/b, should we remove
+// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
+func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
+ if len(mounts) > 0 {
+ // If we have overlappings mounts, remove them from the spec in favor of
+ // the user-added volume mounts
+ destinations := make(map[string]bool)
+ for _, mount := range mounts {
+ destinations[path.Clean(mount.Destination)] = true
+ }
+ // Copy all mounts from spec to defaultMounts, except for
+ // - mounts overridden by a user supplied mount;
+ // - all mounts under /dev if a user supplied /dev is present;
+ mountDev := destinations["/dev"]
+ for _, mount := range configMount {
+ if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
+ if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
+ // filter out everything under /dev if /dev is user-mounted
+ continue
+ }
+
+ logrus.Debugf("Adding mount %s", mount.Destination)
+ mounts = append(mounts, mount)
+ }
+ }
+ return mounts
+ }
+ return configMount
+}
+
+// Ensure mount options on all mounts are correct
+func initFSMounts(inputMounts []spec.Mount) []spec.Mount {
+ var mounts []spec.Mount
+ for _, m := range inputMounts {
+ if m.Type == TypeBind {
+ m.Options = util.ProcessOptions(m.Options)
+ }
+ if m.Type == TypeTmpfs {
+ m.Options = append(m.Options, "tmpcopyup")
+ }
+ mounts = append(mounts, m)
+ }
+ return mounts
+}
diff --git a/pkg/spec/storage_test.go b/pkg/spec/storage_test.go
new file mode 100644
index 000000000..04a9d6976
--- /dev/null
+++ b/pkg/spec/storage_test.go
@@ -0,0 +1,38 @@
+package createconfig
+
+import (
+ "testing"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetVolumeMountsOneVolume(t *testing.T) {
+ data := spec.Mount{
+ Destination: "/foobar",
+ Type: "bind",
+ Source: "/tmp",
+ Options: []string{"ro"},
+ }
+ config := CreateConfig{
+ Volumes: []string{"/tmp:/foobar:ro"},
+ }
+ specMount, _, err := config.getVolumeMounts()
+ assert.NoError(t, err)
+ assert.EqualValues(t, data, specMount[data.Destination])
+}
+
+func TestGetTmpfsMounts(t *testing.T) {
+ data := spec.Mount{
+ Destination: "/homer",
+ Type: "tmpfs",
+ Source: "tmpfs",
+ Options: []string{"rw", "size=787448k", "mode=1777"},
+ }
+ config := CreateConfig{
+ Tmpfs: []string{"/homer:rw,size=787448k,mode=1777"},
+ }
+ tmpfsMount, err := config.getTmpfsMounts()
+ assert.NoError(t, err)
+ assert.EqualValues(t, data, tmpfsMount[data.Destination])
+}
diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go
new file mode 100644
index 000000000..3d1c31b5d
--- /dev/null
+++ b/pkg/systemdgen/systemdgen.go
@@ -0,0 +1,43 @@
+package systemdgen
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+)
+
+var template = `[Unit]
+Description=%s Podman Container
+[Service]
+Restart=%s
+ExecStart=/usr/bin/podman start %s
+ExecStop=/usr/bin/podman stop -t %d %s
+KillMode=none
+Type=forking
+PIDFile=%s
+[Install]
+WantedBy=multi-user.target`
+
+var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"}
+
+// ValidateRestartPolicy checks that the user-provided policy is valid
+func ValidateRestartPolicy(restart string) error {
+ for _, i := range restartPolicies {
+ if i == restart {
+ return nil
+ }
+ }
+ return errors.Errorf("%s is not a valid restart policy", restart)
+}
+
+// CreateSystemdUnitAsString takes variables to create a systemd unit file used to control
+// a libpod container
+func CreateSystemdUnitAsString(name, cid, restart, pidPath string, stopTimeout int) (string, error) {
+ if err := ValidateRestartPolicy(restart); err != nil {
+ return "", err
+ }
+ pidFile := filepath.Join(pidPath, fmt.Sprintf("%s.pid", cid))
+ unit := fmt.Sprintf(template, name, restart, name, stopTimeout, name, pidFile)
+ return unit, nil
+}
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
new file mode 100644
index 000000000..59459807c
--- /dev/null
+++ b/pkg/util/mountOpts.go
@@ -0,0 +1,98 @@
+package util
+
+import (
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+var (
+ // ErrBadMntOption indicates that an invalid mount option was passed.
+ ErrBadMntOption = errors.Errorf("invalid mount option")
+ // ErrDupeMntOption indicates that a duplicate mount option was passed.
+ ErrDupeMntOption = errors.Errorf("duplicate option passed")
+)
+
+// ProcessOptions parses the options for a bind mount and ensures that they are
+// sensible and follow convention.
+func ProcessOptions(options []string) []string {
+ var (
+ foundrw, foundro bool
+ rootProp string
+ )
+ options = append(options, "rbind")
+ for _, opt := range options {
+ switch opt {
+ case "rw":
+ foundrw = true
+ case "ro":
+ foundro = true
+ case "private", "rprivate", "slave", "rslave", "shared", "rshared":
+ rootProp = opt
+ }
+ }
+ if !foundrw && !foundro {
+ options = append(options, "rw")
+ }
+ if rootProp == "" {
+ options = append(options, "rprivate")
+ }
+ return options
+}
+
+// ProcessTmpfsOptions parses the options for a tmpfs mountpoint and ensures
+// that they are sensible and follow convention.
+func ProcessTmpfsOptions(options []string) ([]string, error) {
+ var (
+ foundWrite, foundSize, foundProp, foundMode bool
+ )
+
+ baseOpts := []string{"noexec", "nosuid", "nodev"}
+ for _, opt := range options {
+ // Some options have parameters - size, mode
+ splitOpt := strings.SplitN(opt, "=", 2)
+ switch splitOpt[0] {
+ case "rw", "ro":
+ if foundWrite {
+ return nil, errors.Wrapf(ErrDupeMntOption, "only one of rw and ro can be used")
+ }
+ foundWrite = true
+ baseOpts = append(baseOpts, opt)
+ case "private", "rprivate", "slave", "rslave", "shared", "rshared":
+ if foundProp {
+ return nil, errors.Wrapf(ErrDupeMntOption, "only one root propagation mode can be used")
+ }
+ foundProp = true
+ baseOpts = append(baseOpts, opt)
+ case "size":
+ if foundSize {
+ return nil, errors.Wrapf(ErrDupeMntOption, "only one tmpfs size can be specified")
+ }
+ foundSize = true
+ baseOpts = append(baseOpts, opt)
+ case "mode":
+ if foundMode {
+ return nil, errors.Wrapf(ErrDupeMntOption, "only one tmpfs mode can be specified")
+ }
+ foundMode = true
+ baseOpts = append(baseOpts, opt)
+ case "noexec", "nodev", "nosuid":
+ // Do nothing. We always include these even if they are
+ // not explicitly requested.
+ default:
+ return nil, errors.Wrapf(ErrBadMntOption, "unknown tmpfs option %q", opt)
+ }
+ }
+
+ if !foundWrite {
+ baseOpts = append(baseOpts, "rw")
+ }
+ if !foundSize {
+ baseOpts = append(baseOpts, "size=65536k")
+ }
+ if !foundProp {
+ baseOpts = append(baseOpts, "rprivate")
+ }
+
+ return baseOpts, nil
+}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 14b0c2b55..2a52e5129 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -10,10 +10,12 @@ import (
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
+ "github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
)
@@ -252,3 +254,32 @@ func ParseInputTime(inputTime string) (time.Time, error) {
}
return time.Now().Add(-duration), nil
}
+
+// GetGlobalOpts checks all global flags and generates the command string
+func GetGlobalOpts(c *cliconfig.RunlabelValues) string {
+ globalFlags := map[string]bool{
+ "cgroup-manager": true, "cni-config-dir": true, "conmon": true, "default-mounts-file": true,
+ "hooks-dir": true, "namespace": true, "root": true, "runroot": true,
+ "runtime": true, "storage-driver": true, "storage-opt": true, "syslog": true,
+ "trace": true, "network-cmd-path": true, "config": true, "cpu-profile": true,
+ "log-level": true, "tmpdir": true}
+ const stringSliceType string = "stringSlice"
+
+ var optsCommand []string
+ c.PodmanCommand.Command.Flags().VisitAll(func(f *pflag.Flag) {
+ if !f.Changed {
+ return
+ }
+ if _, exist := globalFlags[f.Name]; exist {
+ if f.Value.Type() == stringSliceType {
+ flagValue := strings.TrimSuffix(strings.TrimPrefix(f.Value.String(), "["), "]")
+ for _, value := range strings.Split(flagValue, ",") {
+ optsCommand = append(optsCommand, fmt.Sprintf("--%s %s", f.Name, value))
+ }
+ } else {
+ optsCommand = append(optsCommand, fmt.Sprintf("--%s %s", f.Name, f.Value.String()))
+ }
+ }
+ })
+ return strings.Join(optsCommand, " ")
+}
diff --git a/pkg/varlinkapi/generate.go b/pkg/varlinkapi/generate.go
index bc600c397..9dc20d582 100644
--- a/pkg/varlinkapi/generate.go
+++ b/pkg/varlinkapi/generate.go
@@ -6,6 +6,7 @@ import (
"encoding/json"
"github.com/containers/libpod/cmd/podman/shared"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
+ "github.com/containers/libpod/pkg/systemdgen"
)
// GenerateKube ...
@@ -28,3 +29,24 @@ func (i *LibpodAPI) GenerateKube(call iopodman.VarlinkCall, name string, service
Service: string(servB),
})
}
+
+// GenerateSystemd ...
+func (i *LibpodAPI) GenerateSystemd(call iopodman.VarlinkCall, nameOrID, restart string, stopTimeout int64, useName bool) error {
+ ctr, err := i.Runtime.LookupContainer(nameOrID)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ timeout := int(ctr.StopTimeout())
+ if stopTimeout >= 0 {
+ timeout = int(stopTimeout)
+ }
+ name := ctr.ID()
+ if useName {
+ name = ctr.Name()
+ }
+ unit, err := systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), restart, ctr.Config().StaticDir, timeout)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyGenerateSystemd(unit)
+}
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index cecddf6b3..20f82a1c6 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -728,7 +728,7 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.
return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label))
}
- cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs)
+ cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "")
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go
new file mode 100644
index 000000000..940e894bc
--- /dev/null
+++ b/test/e2e/generate_systemd_test.go
@@ -0,0 +1,74 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "os"
+)
+
+var _ = Describe("Podman generate systemd", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+
+ It("podman generate systemd on bogus container", func() {
+ session := podmanTest.Podman([]string{"generate", "systemd", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman generate systemd bad restart policy", func() {
+ session := podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "never", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman generate systemd bad timeout value", func() {
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "-1", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman generate systemd", func() {
+ n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "nginx"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman generate systemd with timeout", func() {
+ n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "5", "nginx"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+})
diff --git a/test/e2e/pod_top_test.go b/test/e2e/pod_top_test.go
index 964ee075f..420e4aca9 100644
--- a/test/e2e/pod_top_test.go
+++ b/test/e2e/pod_top_test.go
@@ -93,7 +93,11 @@ var _ = Describe("Podman top", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- result := podmanTest.Podman([]string{"pod", "top", podid, "invalid"})
+ // We need to pass -eo to force executing ps in the Alpine container.
+ // Alpines stripped down ps(1) is accepting any kind of weird input in
+ // contrast to others, such that a `ps invalid` will silently ignore
+ // the wrong input and still print the -ef output instead.
+ result := podmanTest.Podman([]string{"pod", "top", podid, "-eo", "invalid"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(125))
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 849fcc477..fe95db016 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -212,63 +212,6 @@ var _ = Describe("Podman run", func() {
Expect(session.OutputToString()).To(ContainSubstring("100"))
})
- It("podman run with volume flag", func() {
- SkipIfRootless()
- Skip("Skip until we diagnose the regression of volume mounts")
- mountPath := filepath.Join(podmanTest.TempDir, "secrets")
- os.Mkdir(mountPath, 0755)
- session := podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test", mountPath), ALPINE, "cat", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,relatime"))
-
- mountPath = filepath.Join(podmanTest.TempDir, "secrets")
- os.Mkdir(mountPath, 0755)
- session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:ro", mountPath), ALPINE, "cat", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("/run/test ro,relatime"))
-
- mountPath = filepath.Join(podmanTest.TempDir, "secrets")
- os.Mkdir(mountPath, 0755)
- session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:shared", mountPath), ALPINE, "cat", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,relatime, shared"))
- })
-
- It("podman run with --mount flag", func() {
- if podmanTest.Host.Arch == "ppc64le" {
- Skip("skip failing test on ppc64le")
- }
- mountPath := filepath.Join(podmanTest.TempDir, "secrets")
- os.Mkdir(mountPath, 0755)
- session := podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("/run/test rw"))
-
- session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,ro", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("/run/test ro"))
-
- session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,shared", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- found, matches := session.GrepString("/run/test")
- Expect(found).Should(BeTrue())
- Expect(matches[0]).To(ContainSubstring("rw"))
- Expect(matches[0]).To(ContainSubstring("shared"))
-
- mountPath = filepath.Join(podmanTest.TempDir, "scratchpad")
- os.Mkdir(mountPath, 0755)
- session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/run/test", ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,nosuid,nodev,noexec,relatime - tmpfs"))
- })
-
It("podman run with cidfile", func() {
session := podmanTest.Podman([]string{"run", "--cidfile", tempdir + "cidfile", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go
new file mode 100644
index 000000000..e27b2aa55
--- /dev/null
+++ b/test/e2e/run_volume_test.go
@@ -0,0 +1,107 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman run with volumes", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+ })
+
+ It("podman run with volume flag", func() {
+ mountPath := filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ session := podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ found, matches := session.GrepString("/run/test")
+ Expect(found).Should(BeTrue())
+ Expect(matches[0]).To(ContainSubstring("rw"))
+
+ mountPath = filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:ro", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ found, matches = session.GrepString("/run/test")
+ Expect(found).Should(BeTrue())
+ Expect(matches[0]).To(ContainSubstring("ro"))
+
+ mountPath = filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:shared", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ found, matches = session.GrepString("/run/test")
+ Expect(found).Should(BeTrue())
+ Expect(matches[0]).To(ContainSubstring("rw"))
+ Expect(matches[0]).To(ContainSubstring("shared"))
+ })
+
+ It("podman run with --mount flag", func() {
+ if podmanTest.Host.Arch == "ppc64le" {
+ Skip("skip failing test on ppc64le")
+ }
+ mountPath := filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ session := podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test rw"))
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,ro", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test ro"))
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,shared", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ found, matches := session.GrepString("/run/test")
+ Expect(found).Should(BeTrue())
+ Expect(matches[0]).To(ContainSubstring("rw"))
+ Expect(matches[0]).To(ContainSubstring("shared"))
+
+ mountPath = filepath.Join(podmanTest.TempDir, "scratchpad")
+ os.Mkdir(mountPath, 0755)
+ session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/run/test", ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,nosuid,nodev,noexec,relatime - tmpfs"))
+ })
+
+ It("podman run with conflicting volumes errors", func() {
+ mountPath := filepath.Join(podmanTest.TmpDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ session := podmanTest.Podman([]string{"run", "-v", fmt.Sprintf("%s:/run/test", mountPath), "-v", "/tmp:/run/test", ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+})
diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go
index b1d057bfd..f52a2b8fc 100644
--- a/test/e2e/runlabel_test.go
+++ b/test/e2e/runlabel_test.go
@@ -18,6 +18,11 @@ var LsDockerfile = `
FROM alpine:latest
LABEL RUN ls -la`
+var GlobalDockerfile = `
+FROM alpine:latest
+LABEL RUN echo \$GLOBAL_OPTS
+`
+
var _ = Describe("podman container runlabel", func() {
var (
tempdir string
@@ -78,4 +83,18 @@ var _ = Describe("podman container runlabel", func() {
Expect(result.ExitCode()).ToNot(Equal(0))
})
+
+ It("podman container runlabel global options", func() {
+ image := "podman-global-test:ls"
+ podmanTest.BuildImage(GlobalDockerfile, image, "false")
+ result := podmanTest.Podman([]string{"--syslog", "--log-level", "debug", "container", "runlabel", "RUN", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ Expect(result.OutputToString()).To(ContainSubstring("--syslog true"))
+ Expect(result.OutputToString()).To(ContainSubstring("--log-level debug"))
+ result = podmanTest.Podman([]string{"rmi", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ })
})
diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go
index 2d3a5629c..4c2cdb7b5 100644
--- a/test/e2e/top_test.go
+++ b/test/e2e/top_test.go
@@ -87,13 +87,39 @@ var _ = Describe("Podman top", func() {
Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
})
+ It("podman top with ps(1) options", func() {
+ session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"top", session.OutputToString(), "aux"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
+ })
+
+ It("podman top with comma-separated options", func() {
+ session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"top", session.OutputToString(), "user,pid,comm"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
+ })
+
It("podman top on container invalid options", func() {
top := podmanTest.RunTopContainer("")
top.WaitWithDefaultTimeout()
Expect(top.ExitCode()).To(Equal(0))
cid := top.OutputToString()
- result := podmanTest.Podman([]string{"top", cid, "invalid"})
+ // We need to pass -eo to force executing ps in the Alpine container.
+ // Alpines stripped down ps(1) is accepting any kind of weird input in
+ // contrast to others, such that a `ps invalid` will silently ignore
+ // the wrong input and still print the -ef output instead.
+ result := podmanTest.Podman([]string{"top", cid, "-eo", "invalid"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(125))
})