diff options
86 files changed, 2437 insertions, 1576 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.*$'\ @@ -188,6 +188,7 @@ localunit: test/goecho/goecho varlink_generate --tags "$(BUILDTAGS)" \ --succinct $(MAKE) -C contrib/cirrus/packer test + ./contrib/cirrus/lib.sh.t ginkgo: ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 test/e2e/. diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 647ff1e86..24be9bb46 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -267,7 +267,7 @@ func buildCmd(c *cliconfig.BuildValues) error { MemorySwap: memorySwap, ShmSize: c.ShmSize, Ulimit: c.Ulimit, - Volumes: c.Volume, + Volumes: c.Volumes, } options := imagebuildah.BuildOptions{ diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 77156f47a..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 @@ -177,6 +183,12 @@ type InfoValues struct { Format string } +type InitValues struct { + PodmanCommand + All bool + Latest bool +} + type InspectValues struct { PodmanCommand TypeObject string diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 4b0641d82..14451d944 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -17,7 +17,6 @@ func getMainCommands() []*cobra.Command { _loginCommand, _logoutCommand, _mountCommand, - _portCommand, _refreshCommand, _searchCommand, _statsCommand, @@ -45,7 +44,6 @@ func getContainerSubCommands() []*cobra.Command { _commitCommand, _execCommand, _mountCommand, - _portCommand, _refreshCommand, _restoreCommand, _runlabelCommand, diff --git a/cmd/podman/common.go b/cmd/podman/common.go index b02aa5990..8aca08248 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -315,7 +315,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { ) createFlags.Bool( "http-proxy", true, - "Set proxy environment variables in container based on the host proxy vars", + "Set proxy environment variables in the container based on the host proxy vars", ) createFlags.String( "image-volume", cliconfig.DefaultImageVolume, diff --git a/cmd/podman/container.go b/cmd/podman/container.go index b3058bf12..bbf01d1f8 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -56,12 +56,14 @@ var ( _diffCommand, _exportCommand, _createCommand, + _initCommand, _killCommand, _listSubCommand, _logsCommand, _pauseCommand, - _restartCommand, + _portCommand, _pruneContainersCommand, + _restartCommand, _runCommand, _rmCommand, _startCommand, diff --git a/cmd/podman/errors_remote.go b/cmd/podman/errors_remote.go index ab255ea56..1e276be10 100644 --- a/cmd/podman/errors_remote.go +++ b/cmd/podman/errors_remote.go @@ -33,6 +33,8 @@ func outputError(err error) { ne = errors.New(e.Reason) case *iopodman.VolumeNotFound: ne = errors.New(e.Reason) + case *iopodman.InvalidState: + ne = errors.New(e.Reason) case *iopodman.ErrorOccurred: ne = errors.New(e.Reason) default: 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/init.go b/cmd/podman/init.go new file mode 100644 index 000000000..68c80631d --- /dev/null +++ b/cmd/podman/init.go @@ -0,0 +1,64 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + initCommand cliconfig.InitValues + initDescription = `Initialize one or more containers, creating the OCI spec and mounts for inspection. Container names or IDs can be used.` + + _initCommand = &cobra.Command{ + Use: "init [flags] CONTAINER [CONTAINER...]", + Short: "Initialize one or more containers", + Long: initDescription, + RunE: func(cmd *cobra.Command, args []string) error { + initCommand.InputArgs = args + initCommand.GlobalFlags = MainGlobalOpts + initCommand.Remote = remoteclient + return initCmd(&initCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman init --latest + podman init 3c45ef19d893 + podman init test1`, + } +) + +func init() { + initCommand.Command = _initCommand + initCommand.SetHelpTemplate(HelpTemplate()) + initCommand.SetUsageTemplate(UsageTemplate()) + flags := initCommand.Flags() + flags.BoolVarP(&initCommand.All, "all", "a", false, "Initialize all containers") + flags.BoolVarP(&initCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +// initCmd initializes a container +func initCmd(c *cliconfig.InitValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "initCmd") + defer span.Finish() + } + + ctx := getContext() + + runtime, err := adapter.GetRuntime(ctx, &c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ok, failures, err := runtime.InitContainers(ctx, c) + if err != nil { + return err + } + return printCmdResults(ok, failures) +} 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/main.go b/cmd/podman/main.go index f501ee674..787dd55c0 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -39,12 +39,14 @@ var mainCommands = []*cobra.Command{ &_imagesCommand, _importCommand, _infoCommand, + _initCommand, &_inspectCommand, _killCommand, _loadCommand, _logsCommand, _pauseCommand, podCommand.Command, + _portCommand, &_psCommand, _pullCommand, _pushCommand, 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/port.go b/cmd/podman/port.go index 7a9f01fe6..1bd2d623e 100644 --- a/cmd/podman/port.go +++ b/cmd/podman/port.go @@ -6,8 +6,7 @@ import ( "strings" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -51,10 +50,7 @@ func portCmd(c *cliconfig.PortValues) error { var ( userProto, containerName string userPort int - container *libpod.Container - containers []*libpod.Container ) - args := c.InputArgs if c.Latest && c.All { @@ -66,9 +62,6 @@ func portCmd(c *cliconfig.PortValues) error { if len(args) == 0 && !c.Latest && !c.All { return errors.Errorf("you must supply a running container name or id") } - if !c.Latest && !c.All { - containerName = args[0] - } port := "" if len(args) > 1 && !c.Latest { @@ -98,36 +91,14 @@ func portCmd(c *cliconfig.PortValues) error { } } - runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if !c.Latest && !c.All { - container, err = runtime.LookupContainer(containerName) - if err != nil { - return errors.Wrapf(err, "unable to find container %s", containerName) - } - containers = append(containers, container) - } else if c.Latest { - container, err = runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get last created container") - } - containers = append(containers, container) - } else { - containers, err = runtime.GetRunningContainers() - if err != nil { - return errors.Wrapf(err, "unable to get all containers") - } - } - + containers, err := runtime.Port(c) for _, con := range containers { - if state, _ := con.State(); state != libpod.ContainerStateRunning { - continue - } - portmappings, err := con.PortMappings() if err != nil { return err 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/search.go b/cmd/podman/search.go index 13948aef0..b236f3055 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -118,16 +118,3 @@ func searchToGeneric(params []image.SearchResult) (genericParams []interface{}) } return genericParams } - -func genSearchOutputMap() map[string]string { - io := image.SearchResult{} - v := reflect.Indirect(reflect.ValueOf(io)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 48476e177..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 } } @@ -726,18 +711,8 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. return config, nil } -type namespace interface { - IsContainer() bool - Container() string -} - 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/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/shared/workers.go b/cmd/podman/shared/workers.go index 112af89cc..b6e3f10e7 100644 --- a/cmd/podman/shared/workers.go +++ b/cmd/podman/shared/workers.go @@ -110,9 +110,14 @@ func (p *Pool) newWorker(slot int) { func DefaultPoolSize(name string) int { numCpus := runtime.NumCPU() switch name { + case "init": + fallthrough case "kill": + fallthrough case "pause": + fallthrough case "rm": + fallthrough case "unpause": if numCpus <= 3 { return numCpus * 3 diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 309f9765a..ace81646c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -641,6 +641,14 @@ method StartContainer(name: string) -> (container: string) # ~~~ method StopContainer(name: string, timeout: int) -> (container: string) +# InitContainer initializes the given container. It accepts a container name or +# ID, and will initialize the container matching that ID if possible, and error +# if not. Containers can only be initialized when they are in the Created or +# Exited states. Initialization prepares a container to be started, but does not +# start the container. It is intended to be used to debug a container's state +# prior to starting it. +method InitContainer(name: string) -> (container: string) + # RestartContainer will restart a running container given a container name or ID and timeout value. The timeout # value is the time before a forcible stop is used to stop the container. If the container cannot be found by # name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise, the ID of the @@ -1210,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) @@ -1225,7 +1235,7 @@ error PodNotFound (name: string, reason: string) # VolumeNotFound means the volume could not be found by the name or ID in local storage. error VolumeNotFound (id: string, reason: string) -# PodContainerError means a container associated with a pod failed to preform an operation. It contains +# PodContainerError means a container associated with a pod failed to perform an operation. It contains # a container ID of the container that failed. error PodContainerError (podname: string, errors: []PodContainerErrorData) @@ -1233,6 +1243,9 @@ error PodContainerError (podname: string, errors: []PodContainerErrorData) # the pod ID. error NoContainersInPod (name: string) +# InvalidState indicates that a container or pod was in an improper state for the requested operation +error InvalidState (id: string, reason: string) + # ErrorOccurred is a generic error for an error that occurs during the execution. The actual error message # is includes as part of the error's text. error ErrorOccurred (reason: string) @@ -1241,4 +1254,4 @@ error ErrorOccurred (reason: string) error RuntimeError (reason: string) # The Podman endpoint requires that you use a streaming connection. -error WantsMoreRequired (reason: string) +error WantsMoreRequired (reason: string)
\ No newline at end of file diff --git a/commands.md b/commands.md index 1c05640f2..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 | @@ -34,6 +36,7 @@ Command | Descr [podman-images(1)](/docs/podman-images.1.md) | List images in local storage | [![...](/docs/play.png)](https://podman.io/asciinema/podman/images/) | [Here](https://github.com/containers/Demos/blob/master/podman_cli/podman_images.sh) [podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image | [podman-info(1)](/docs/podman-info.1.md) | Display system information | +[podman-init(1)](/docs/podman-init.1.md) | Initialize a container | [podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image | [![...](/docs/play.png)](https://asciinema.org/a/133418) [podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers | [podman-load(1)](/docs/podman-load.1.md) | Load an image from a container image archive | diff --git a/completions/bash/podman b/completions/bash/podman index b5963f8b9..e3c0c1dbf 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -780,6 +780,10 @@ _podman_container_export() { _podman_export } +_podman_container_init() { + _podman_init +} + _podman_container_inspect() { _podman_inspect } @@ -915,6 +919,7 @@ _podman_generate() { " subcommands=" kube + systemd " __podman_subcommands "$subcommands $aliases" && return @@ -2223,6 +2228,27 @@ _podman_ps() { _complete_ "$options_with_args" "$boolean_options" } +_podman_init() { + local boolean_options=" + --all + -a + --help + -h + --latest + -l + " + local options_with_args=" + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_unpauseable + ;; + esac +} + _podman_start() { local options_with_args=" --detach-keys @@ -2435,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 6b86aa4d4..43eb3c057 100755 --- a/contrib/cirrus/build_vm_images.sh +++ b/contrib/cirrus/build_vm_images.sh @@ -3,27 +3,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var " -CNI_COMMIT $CNI_COMMIT -CRIO_COMMIT $CRIO_COMMIT -RUNC_COMMIT $RUNC_COMMIT -PACKER_BUILDS $PACKER_BUILDS -BUILT_IMAGE_SUFFIX $BUILT_IMAGE_SUFFIX -CENTOS_BASE_IMAGE $CENTOS_BASE_IMAGE -UBUNTU_BASE_IMAGE $UBUNTU_BASE_IMAGE -FEDORA_BASE_IMAGE $FEDORA_BASE_IMAGE -FAH_BASE_IMAGE $FAH_BASE_IMAGE -RHEL_BASE_IMAGE $RHEL_BASE_IMAGE -RHSM_COMMAND $RHSM_COMMAND -SERVICE_ACCOUNT $SERVICE_ACCOUNT -GCE_SSH_USERNAME $GCE_SSH_USERNAME -GCP_PROJECT_ID $GCP_PROJECT_ID -PACKER_VER $PACKER_VER -SCRIPT_BASE $SCRIPT_BASE -PACKER_BASE $PACKER_BASE -" - -record_timestamp "cache-image build start" +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 show_env_vars @@ -61,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 95387ff49..5b73f0c6c 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -3,21 +3,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var " -GOSRC $GOSRC -SCRIPT_BASE $SCRIPT_BASE -OS_RELEASE_ID $OS_RELEASE_ID -OS_RELEASE_VER $OS_RELEASE_VER -CONTAINER_RUNTIME $CONTAINER_RUNTIME -" - -exit_handler() { - set +ex - record_timestamp "integration test end" -} -trap exit_handler EXIT - -record_timestamp "integration test start" +req_env_var GOSRC SCRIPT_BASE OS_RELEASE_ID OS_RELEASE_VER CONTAINER_RUNTIME cd "$GOSRC" @@ -39,7 +25,7 @@ then exit $? elif [[ "$SPECIALMODE" == "rootless" ]] then - req_env_var "ROOTLESS_USER $ROOTLESS_USER" + req_env_var ROOTLESS_USER set -x ssh $ROOTLESS_USER@localhost \ -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no \ diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index d663616b2..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} @@ -33,15 +32,27 @@ then source "$HOME/$ENVLIB" fi -# Pass in a line delimited list of, space delimited name/value pairs -# exit non-zero with helpful error message if any value is empty +# Pass in a list of one or more envariable names; exit non-zero with +# helpful error message if any value is empty req_env_var() { - echo "$1" | while read NAME VALUE - do - if [[ -n "$NAME" ]] && [[ -z "$VALUE" ]] - then - echo "Required env. var. \$$NAME is not set" - exit 9 + # Provide context. If invoked from function use its name; else script name + local caller=${FUNCNAME[1]} + if [[ -n "$caller" ]]; then + # Indicate that it's a function name + caller="$caller()" + else + # Not called from a function: use script name + caller=$(basename $0) + fi + + # Usage check + [[ -n "$1" ]] || die 1 "FATAL: req_env_var: invoked without arguments" + + # Each input arg is an envariable name, e.g. HOME PATH etc. Expand each. + # If any is empty, bail out and explain why. + for i; do + if [[ -z "${!i}" ]]; then + die 9 "FATAL: $caller requires \$$i to be non-empty" fi done } @@ -97,20 +108,14 @@ PACKER_BUILDS $PACKER_BUILDS # Unset environment variables not needed for testing purposes clean_env() { - req_env_var " - UNSET_ENV_VARS $UNSET_ENV_VARS - " + req_env_var UNSET_ENV_VARS echo "Unsetting $(echo $UNSET_ENV_VARS | wc -w) environment variables" unset -v UNSET_ENV_VARS $UNSET_ENV_VARS || true # don't fail on read-only } die() { - req_env_var " - 1 $1 - 2 $2 - " - echo "$2" - exit $1 + echo "${2:-FATAL ERROR (but no message given!) in ${FUNCNAME[1]}()}" + exit ${1:-1} } # Return a GCE image-name compatible string representation of distribution name @@ -135,10 +140,8 @@ stub() { } ircmsg() { - req_env_var " - CIRRUS_TASK_ID $CIRRUS_TASK_ID - @ $@ - " + req_env_var CIRRUS_TASK_ID + [[ -n "$*" ]] || die 9 "ircmsg() invoked without args" # Sometimes setup_environment.sh didn't run SCRIPT="$(dirname $0)/podbot.py" NICK="podbot_$CIRRUS_TASK_ID" @@ -149,22 +152,8 @@ ircmsg() { set -e } -record_timestamp() { - set +x # sometimes it's turned on - req_env_var "TIMESTAMPS_FILEPATH $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 $ROOTLESS_USER - GOSRC $GOSRC - ENVLIB $ENVLIB - " + req_env_var ROOTLESS_USER GOSRC ENVLIB if passwd --status $ROOTLESS_USER then @@ -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" @@ -220,7 +206,7 @@ setup_rootless() { # Helper/wrapper script to only show stderr/stdout on non-zero exit install_ooe() { - req_env_var "SCRIPT_BASE $SCRIPT_BASE" + req_env_var SCRIPT_BASE echo "Installing script to mask stdout/stderr unless non-zero exit." sudo install -D -m 755 "/tmp/libpod/$SCRIPT_BASE/ooe.sh" /usr/local/bin/ooe.sh } @@ -241,10 +227,7 @@ EOF install_cni_plugins() { echo "Installing CNI Plugins from commit $CNI_COMMIT" - req_env_var " - GOPATH $GOPATH - CNI_COMMIT $CNI_COMMIT - " + req_env_var GOPATH CNI_COMMIT DEST="$GOPATH/src/github.com/containernetworking/plugins" rm -rf "$DEST" ooe.sh git clone "https://github.com/containernetworking/plugins.git" "$DEST" @@ -272,11 +255,7 @@ install_runc(){ OS_RELEASE_ID=$(os_release_id) echo "Installing RunC from commit $RUNC_COMMIT" echo "Platform is $OS_RELEASE_ID" - req_env_var " - GOPATH $GOPATH - RUNC_COMMIT $RUNC_COMMIT - OS_RELEASE_ID $OS_RELEASE_ID - " + req_env_var GOPATH RUNC_COMMIT OS_RELEASE_ID if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then echo "Running make install.libseccomp.sudo for ubuntu" if ! [[ -d "/tmp/libpod" ]] @@ -295,7 +274,7 @@ install_runc(){ install_buildah() { echo "Installing buildah from latest upstream master" - req_env_var "GOPATH $GOPATH" + req_env_var GOPATH DEST="$GOPATH/src/github.com/containers/buildah" rm -rf "$DEST" ooe.sh git clone https://github.com/containers/buildah "$DEST" @@ -307,10 +286,7 @@ install_buildah() { # Requires $GOPATH and $CRIO_COMMIT to be set install_conmon(){ echo "Installing conmon from commit $CRIO_COMMIT" - req_env_var " - GOPATH $GOPATH - CRIO_COMMIT $CRIO_COMMIT - " + req_env_var GOPATH CRIO_COMMIT DEST="$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" rm -rf "$DEST" ooe.sh git clone https://github.com/kubernetes-sigs/cri-o.git "$DEST" @@ -327,9 +303,7 @@ install_criu(){ echo "Installing CRIU" echo "Installing CRIU from commit $CRIU_COMMIT" echo "Platform is $OS_RELEASE_ID" - req_env_var " - CRIU_COMMIT $CRIU_COMMIT - " + req_env_var CRIU_COMMIT if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then ooe.sh sudo -E add-apt-repository -y ppa:criu/ppa @@ -418,10 +392,7 @@ ubuntu_finalize(){ rhel_exit_handler() { set +ex - req_env_var " - GOPATH $GOPATH - RHSMCMD $RHSMCMD - " + req_env_var GOPATH RHSMCMD cd / sudo rm -rf "$RHSMCMD" sudo rm -rf "$GOPATH" @@ -431,9 +402,7 @@ rhel_exit_handler() { } rhsm_enable() { - req_env_var " - RHSM_COMMAND $RHSM_COMMAND - " + req_env_var RHSM_COMMAND export GOPATH="$(mktemp -d)" export RHSMCMD="$(mktemp)" trap "rhel_exit_handler" EXIT diff --git a/contrib/cirrus/lib.sh.t b/contrib/cirrus/lib.sh.t new file mode 100755 index 000000000..ce51f8ad2 --- /dev/null +++ b/contrib/cirrus/lib.sh.t @@ -0,0 +1,81 @@ +#!/bin/bash +# +# Unit tests for some functions in lib.sh +# +source $(dirname $0)/lib.sh + +# Iterator and return code; updated in test functions +testnum=0 +rc=0 + +function check_result { + testnum=$(expr $testnum + 1) + if [ "$1" = "$2" ]; then + echo "ok $testnum $3 = $1" + else + echo "not ok $testnum $3" + echo "# expected: $2" + echo "# actual: $1" + rc=1 + fi +} + +############################################################################### +# tests for die() + +function test_die() { + local input_status=$1 + local input_msg=$2 + local expected_status=$3 + local expected_msg=$4 + + local msg + msg=$(die $input_status "$input_msg") + local status=$? + + check_result "$msg" "$expected_msg" "die $input_status $input_msg" +} + +test_die 1 "a message" 1 "a message" +test_die 2 "" 2 "FATAL ERROR (but no message given!) in test_die()" +test_die '' '' 1 "FATAL ERROR (but no message given!) in test_die()" + +############################################################################### +# tests for req_env_var() + +function test_rev() { + local input_args=$1 + local expected_status=$2 + local expected_msg=$3 + + # bash gotcha: doing 'local msg=...' on one line loses exit status + local msg + msg=$(req_env_var $input_args) + local status=$? + + check_result "$msg" "$expected_msg" "req_env_var $input_args" + check_result "$status" "$expected_status" "req_env_var $input_args (rc)" +} + +# error if called with no args +test_rev '' 1 'FATAL: req_env_var: invoked without arguments' + +# error if desired envariable is unset +unset FOO BAR +test_rev FOO 9 'FATAL: test_rev() requires $FOO to be non-empty' +test_rev BAR 9 'FATAL: test_rev() requires $BAR to be non-empty' + +# OK if desired envariable is unset +FOO=1 +test_rev FOO 0 '' + +# ...but error if any single desired one is unset +test_rev "FOO BAR" 9 'FATAL: test_rev() requires $BAR to be non-empty' + +# ...and OK if all args are set +BAR=1 +test_rev "FOO BAR" 0 '' + +############################################################################### + +exit $rc diff --git a/contrib/cirrus/packer/centos_setup.sh b/contrib/cirrus/packer/centos_setup.sh index d947a1d7f..91b1963c2 100644 --- a/contrib/cirrus/packer/centos_setup.sh +++ b/contrib/cirrus/packer/centos_setup.sh @@ -8,12 +8,7 @@ set -e # Load in library (copied by packer, before this script was run) source /tmp/libpod/$SCRIPT_BASE/lib.sh -req_env_var " -SCRIPT_BASE $SCRIPT_BASE -CNI_COMMIT $CNI_COMMIT -CRIO_COMMIT $CRIO_COMMIT -CRIU_COMMIT $CRIU_COMMIT -" +req_env_var SCRIPT_BASE CNI_COMMIT CRIO_COMMIT CRIU_COMMIT install_ooe diff --git a/contrib/cirrus/packer/fah_setup.sh b/contrib/cirrus/packer/fah_setup.sh index 2e053b396..18c4db0af 100644 --- a/contrib/cirrus/packer/fah_setup.sh +++ b/contrib/cirrus/packer/fah_setup.sh @@ -8,9 +8,7 @@ set -e # Load in library (copied by packer, before this script was run) source /tmp/libpod/$SCRIPT_BASE/lib.sh -req_env_var " -SCRIPT_BASE $SCRIPT_BASE -" +req_env_var SCRIPT_BASE install_ooe diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index 84aee7667..36a65eb71 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -8,14 +8,7 @@ set -e # Load in library (copied by packer, before this script was run) source /tmp/libpod/$SCRIPT_BASE/lib.sh -req_env_var " -SCRIPT_BASE $SCRIPT_BASE -FEDORA_CNI_COMMIT $FEDORA_CNI_COMMIT -CNI_COMMIT $CNI_COMMIT -CRIO_COMMIT $CRIO_COMMIT -CRIU_COMMIT $CRIU_COMMIT -RUNC_COMMIT $RUNC_COMMIT -" +req_env_var SCRIPT_BASE FEDORA_CNI_COMMIT CNI_COMMIT CRIO_COMMIT CRIU_COMMIT RUNC_COMMIT install_ooe diff --git a/contrib/cirrus/packer/image-builder-image_base-setup.sh b/contrib/cirrus/packer/image-builder-image_base-setup.sh index 8cf9fd8ab..43cfa7180 100644 --- a/contrib/cirrus/packer/image-builder-image_base-setup.sh +++ b/contrib/cirrus/packer/image-builder-image_base-setup.sh @@ -11,12 +11,7 @@ set -e # Load in library (copied by packer, before this script was run) source $GOSRC/$SCRIPT_BASE/lib.sh -req_env_var " - TIMESTAMP $TIMESTAMP - GOSRC $GOSRC - SCRIPT_BASE $SCRIPT_BASE - PACKER_BASE $PACKER_BASE -" +req_env_var TIMESTAMP GOSRC SCRIPT_BASE PACKER_BASE install_ooe diff --git a/contrib/cirrus/packer/rhel_base-setup.sh b/contrib/cirrus/packer/rhel_base-setup.sh index fbf9f61af..8d5892d7d 100644 --- a/contrib/cirrus/packer/rhel_base-setup.sh +++ b/contrib/cirrus/packer/rhel_base-setup.sh @@ -10,9 +10,7 @@ set -e # Load in library (copied by packer, before this script was run) source $GOSRC/$SCRIPT_BASE/lib.sh -req_env_var " - RHSM_COMMAND $RHSM_COMMAND -" +req_env_var RHSM_COMMAND install_ooe diff --git a/contrib/cirrus/packer/rhel_setup.sh b/contrib/cirrus/packer/rhel_setup.sh index 20be97f9b..45f5c3e9b 100644 --- a/contrib/cirrus/packer/rhel_setup.sh +++ b/contrib/cirrus/packer/rhel_setup.sh @@ -8,13 +8,7 @@ set -e # Load in library (copied by packer, before this script was run) source /tmp/libpod/$SCRIPT_BASE/lib.sh -req_env_var " -SCRIPT_BASE $SCRIPT_BASE -CNI_COMMIT $CNI_COMMIT -CRIO_COMMIT $CRIO_COMMIT -CRIU_COMMIT $CRIU_COMMIT -RHSM_COMMAND $RHSM_COMMAND -" +req_env_var SCRIPT_BASE CNI_COMMIT CRIO_COMMIT CRIU_COMMIT RHSM_COMMAND install_ooe diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index e84566ce3..d3ac8bddb 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -8,13 +8,7 @@ set -e # Load in library (copied by packer, before this script was run) source /tmp/libpod/$SCRIPT_BASE/lib.sh -req_env_var " -SCRIPT_BASE $SCRIPT_BASE -CNI_COMMIT $CNI_COMMIT -CRIO_COMMIT $CRIO_COMMIT -CRIU_COMMIT $CRIU_COMMIT -RUNC_COMMIT $RUNC_COMMIT -" +req_env_var SCRIPT_BASE CNI_COMMIT CRIO_COMMIT CRIU_COMMIT RUNC_COMMIT install_ooe diff --git a/contrib/cirrus/rootless_test.sh b/contrib/cirrus/rootless_test.sh index 09c57f993..8351214f3 100755 --- a/contrib/cirrus/rootless_test.sh +++ b/contrib/cirrus/rootless_test.sh @@ -6,11 +6,7 @@ source $HOME/.bash_profile cd $GOSRC source $(dirname $0)/lib.sh -req_env_var " -GOSRC $GOSRC -OS_RELEASE_ID $OS_RELEASE_ID -OS_RELEASE_VER $OS_RELEASE_VER -" +req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER if [[ "$UID" == "0" ]] then @@ -23,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_container_environment.sh b/contrib/cirrus/setup_container_environment.sh index 23df4fe8b..eda6f6167 100755 --- a/contrib/cirrus/setup_container_environment.sh +++ b/contrib/cirrus/setup_container_environment.sh @@ -3,11 +3,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var " -GOSRC $GOSRC -OS_RELEASE_ID $OS_RELEASE_ID -CONTAINER_RUNTIME $CONTAINER_RUNTIME -" +req_env_var GOSRC OS_RELEASE_ID CONTAINER_RUNTIME DIST=$OS_RELEASE_ID IMAGE=${DIST}podmanbuild diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 3818abbc7..2f9b1d796 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -4,14 +4,7 @@ set -e source $(dirname $0)/lib.sh -record_timestamp "env. setup start" - -req_env_var " -USER $USER -HOME $HOME -ENVLIB $ENVLIB -SCRIPT_BASE $SCRIPT_BASE -CIRRUS_BUILD_ID $CIRRUS_BUILD_ID" +req_env_var USER HOME ENVLIB SCRIPT_BASE CIRRUS_BUILD_ID [[ "$SHELL" =~ "bash" ]] || chsh -s /bin/bash @@ -91,5 +84,3 @@ then fi show_env_vars - -record_timestamp "env. setup end" diff --git a/contrib/cirrus/success.sh b/contrib/cirrus/success.sh index 2b0cf4655..c4e150514 100755 --- a/contrib/cirrus/success.sh +++ b/contrib/cirrus/success.sh @@ -4,10 +4,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var " - CIRRUS_BRANCH $CIRRUS_BRANCH - CIRRUS_BUILD_ID $CIRRUS_BUILD_ID -" +req_env_var CIRRUS_BRANCH CIRRUS_BUILD_ID REF=$(basename $CIRRUS_BRANCH) # PR number or branch named URL="https://cirrus-ci.com/build/$CIRRUS_BUILD_ID" diff --git a/contrib/cirrus/system_test.sh b/contrib/cirrus/system_test.sh index cb179407a..dd5ef511d 100755 --- a/contrib/cirrus/system_test.sh +++ b/contrib/cirrus/system_test.sh @@ -3,11 +3,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var " -GOSRC $GOSRC -OS_RELEASE_ID $OS_RELEASE_ID -OS_RELEASE_VER $OS_RELEASE_VER -" +req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER clean_env 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 4ace19d10..0e8c9e2e2 100755 --- a/contrib/cirrus/unit_test.sh +++ b/contrib/cirrus/unit_test.sh @@ -3,13 +3,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var " -GOSRC $GOSRC -OS_RELEASE_ID $OS_RELEASE_ID -OS_RELEASE_VER $OS_RELEASE_VER -" - -record_timestamp "unit test start" +req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER clean_env @@ -18,5 +12,3 @@ cd "$GOSRC" make install.tools make localunit make - -record_timestamp "unit test end" diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md index 1ba957480..564d791fa 100644 --- a/docs/podman-container.1.md +++ b/docs/podman-container.1.md @@ -22,6 +22,7 @@ The container command allows you to manage containers | exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | | exists | [podman-container-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage | | export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | +| init | [podman-init(1)](podman-init.1.md) | Initialize a container | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | | list | [podman-ps(1)](podman-ps.1.md) | List the containers on the system.(alias ls) | diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 884a8adcc..6d7d983b6 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -250,7 +250,17 @@ By default proxy environment variables are passed into the container if set for the podman process. This can be disabled by setting the `--http-proxy` option to `false`. The environment variables passed in include `http_proxy`, `https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of -those. +those. This option is only needed when the host system must use a proxy but +the container should not use any proxy. Proxy environment variables specified +for the container in any other way will override the values that would have +been passed thru from the host. (Other ways to specify the proxy for the +container include passing the values with the `--env` flag, or hardcoding the +proxy environment at container build time.) + +For example, to disable passing these environment variables from host to +container: + +`--http-proxy=false` Defaults to `true` @@ -269,7 +279,7 @@ The following example maps uids 0-2000 in the container to the uids 30000-31999 Add additional groups to run as -**--healthchech**="" +**--healthcheck**="" Set or alter a healthcheck for a container. The value must be of the format of: 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-init.1.md b/docs/podman-init.1.md new file mode 100644 index 000000000..f43757f62 --- /dev/null +++ b/docs/podman-init.1.md @@ -0,0 +1,41 @@ +% podman-init(1) + +## NAME +podman\-init - Initialize one or more containers + +## SYNOPSIS +**podman init** [*options*] *container* ... + +## DESCRIPTION +Initialize one or more containers. +You may use container IDs or names as input. +Initializing a container performs all tasks necessary for starting the container (mounting filesystems, creating an OCI spec, initializing the container network) but does not start the container. +If a container is not initialized, the `podman start` and `podman run` commands will do so automatically prior to starting it. +This command is intended to be used for inspecting or modifying the container's filesystem or OCI spec prior to starting it. +This can be used to inspect the container before it runs, or debug why a container is failing to run. + +## OPTIONS + +**--all, -a** + +Initialize all containers. Containers that have already initialized (including containers that have been started and are running) are ignored. + +**--latest, -l** +Instead of providing the container name or ID, use the last created container. If you use methods other than Podman +to run containers such as CRI-O, the last started container could be from either of those methods. + +The latest option is not supported on the remote client. + +## EXAMPLE + +podman init 35480fc9d568 + +podman init test1 + +podman init --latest + +## SEE ALSO +podman(1), podman-start(1) + +## HISTORY +April 2019, Originally compiled by Matthew Heon <mheon@redhat.com> diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index a0c17652a..9efb7f51c 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -257,7 +257,17 @@ By default proxy environment variables are passed into the container if set for the podman process. This can be disabled by setting the `--http-proxy` option to `false`. The environment variables passed in include `http_proxy`, `https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of -those. +those. This option is only needed when the host system must use a proxy but +the container should not use any proxy. Proxy environment variables specified +for the container in any other way will override the values that would have +been passed thru from the host. (Other ways to specify the proxy for the +container include passing the values with the `--env` flag, or hardcoding the +proxy environment at container build time.) + +For example, to disable passing these environment variables from host to +container: + +`--http-proxy=false` Defaults to `true` @@ -277,7 +287,7 @@ The example maps gids 0-2000 in the container to the gids 30000-31999 on the hos Add additional groups to run as -**--healthchech**="" +**--healthcheck**="" Set or alter a healthcheck for a container. The value must be of the format of: @@ -583,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* @@ -1141,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.1.md b/docs/podman.1.md index 9c0ca8a7a..ef12cf1cc 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -147,6 +147,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-images(1)](podman-images.1.md) | List images in local storage. | | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | [podman-info(1)](podman-info.1.md) | Displays Podman related system information. | +| [podman-init(1)](podman-init.1.md) | Initialize a container | | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | | [podman-load(1)](podman-load.1.md) | Load an image from a container image archive into container storage. | diff --git a/install.md b/install.md index 548b38c1b..bd7f326c3 100644 --- a/install.md +++ b/install.md @@ -8,6 +8,8 @@ sudo pacman -S podman ``` +If you have problems when running podman in [rootless](README.md#rootless) mode follow [these instructions](https://wiki.archlinux.org/index.php/Linux_Containers#Enable_support_to_run_unprivileged_containers_(optional)) + #### [Fedora](https://www.fedoraproject.org), [CentOS](https://www.centos.org) ```bash diff --git a/libpod/container_api.go b/libpod/container_api.go index 465b23831..5bfd869b3 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -40,7 +40,7 @@ func (c *Container) Init(ctx context.Context) (err error) { if !(c.state.State == ContainerStateConfigured || c.state.State == ContainerStateStopped || c.state.State == ContainerStateExited) { - return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID()) + return errors.Wrapf(ErrCtrStateInvalid, "container %s has already been created in runtime", c.ID()) } // don't recursively start diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 7febf6966..a791df491 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -811,8 +811,9 @@ func (c *Container) cleanupRuntime(ctx context.Context) error { span.SetTag("struct", "container") defer span.Finish() - // If the container is not ContainerStateStopped, do nothing - if c.state.State != ContainerStateStopped { + // If the container is not ContainerStateStopped or + // ContainerStateCreated, do nothing. + if c.state.State != ContainerStateStopped && c.state.State != ContainerStateCreated { return nil } @@ -825,9 +826,14 @@ func (c *Container) cleanupRuntime(ctx context.Context) error { return err } - // Our state is now Exited, as we've removed ourself from - // the runtime. - c.state.State = ContainerStateExited + // If we were Stopped, we are now Exited, as we've removed ourself + // from the runtime. + // If we were Created, we are now Configured. + if c.state.State == ContainerStateStopped { + c.state.State = ContainerStateExited + } else if c.state.State == ContainerStateCreated { + c.state.State = ContainerStateConfigured + } if c.valid { if err := c.save(); err != nil { 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..6b8d97fd9 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 } } 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 9ec897a60..d575bc9b0 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -18,6 +18,7 @@ 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/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -133,6 +134,43 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa return pool.Run() } +// InitContainers initializes container(s) based on CLI inputs. +// Returns list of successful id(s), map of failed id(s) to errors, or a general +// error not from the container. +func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) { + maxWorkers := shared.DefaultPoolSize("init") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum init workers to %d", maxWorkers) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return nil, nil, err + } + + pool := shared.NewPool("init", maxWorkers, len(ctrs)) + for _, c := range ctrs { + ctr := c + + pool.Add(shared.Job{ + ctr.ID(), + func() error { + err := ctr.Init(ctx) + if err != nil { + // If we're initializing all containers, ignore invalid state errors + if cli.All && errors.Cause(err) == libpod.ErrCtrStateInvalid { + return nil + } + return err + } + return nil + }, + }) + } + return pool.Run() +} + // RemoveContainers removes container(s) based on CLI inputs. func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) { var ( @@ -876,3 +914,47 @@ func cleanupContainer(ctx context.Context, ctr *libpod.Container, runtime *Local } return nil } + +// Port displays port information about existing containers +func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) { + var ( + portContainers []*Container + containers []*libpod.Container + err error + ) + + if !c.All { + containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime) + } else { + containers, err = r.Runtime.GetRunningContainers() + } + if err != nil { + return nil, err + } + + //Convert libpod containers to adapter Containers + for _, con := range containers { + if state, _ := con.State(); state != libpod.ContainerStateRunning { + continue + } + portContainers = append(portContainers, &Container{con}) + } + 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 a3a48a564..201249fc3 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -18,6 +18,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/docker/pkg/term" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -63,6 +64,19 @@ func (c *Container) Unpause() error { return err } +func (c *Container) PortMappings() ([]ocicni.PortMapping, error) { + // First check if the container belongs to a network namespace (like a pod) + // Taken from libpod portmappings() + if len(c.config.NetNsCtr) > 0 { + netNsCtr, err := c.Runtime.LookupContainer(c.config.NetNsCtr) + if err != nil { + return nil, errors.Wrapf(err, "unable to lookup network namespace for container %s", c.ID()) + } + return netNsCtr.PortMappings() + } + return c.config.PortMappings, nil +} + // Config returns a container config func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { // TODO the Spec being returned is not populated. Matt and I could not figure out why. Will defer @@ -234,6 +248,40 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa return ok, failures, nil } +// InitContainers initializes container(s) based on Varlink. +// It returns a list of successful ID(s), a map of failed container ID to error, +// or an error if a more general error occurred. +func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return nil, nil, err + } + + for _, id := range ids { + initialized, err := iopodman.InitContainer().Call(r.Conn, id) + if err != nil { + if cli.All { + switch err.(type) { + case *iopodman.InvalidState: + ok = append(ok, initialized) + default: + failures[id] = err + } + } else { + failures[id] = err + } + } else { + ok = append(ok, initialized) + } + } + return ok, failures, nil +} + // KillContainers sends signal to container(s) based on varlink. // Returns list of successful id(s), map of failed id(s) + error, or error not from container func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) { @@ -888,3 +936,28 @@ func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([ func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) { return nil, nil, errors.New("container cleanup not supported for remote clients") } + +// Port displays port information about existing containers +func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) { + var ( + containers []*Container + err error + ) + // This one is a bit odd because when all is used, we only use running containers. + if !c.All { + containers, err = r.GetContainersByContext(false, c.Latest, c.InputArgs) + } else { + // we need to only use running containers if all + filters := []string{libpod.ContainerStateRunning.String()} + containers, err = r.LookupContainersWithStatus(filters) + } + if err != nil { + return nil, err + } + 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/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 6102daccf..4986d16f7 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -889,3 +889,20 @@ func (r *LocalRuntime) GenerateKube(c *cliconfig.GenerateKubeValues) (*v1.Pod, * err = json.Unmarshal([]byte(reply.Service), &service) return &pod, &service, err } + +// GetContainersByContext looks up containers based on the cli input of all, latest, or a list +func (r *LocalRuntime) GetContainersByContext(all bool, latest bool, namesOrIDs []string) ([]*Container, error) { + var containers []*Container + cids, err := iopodman.GetContainersByContext().Call(r.Conn, all, latest, namesOrIDs) + if err != nil { + return nil, err + } + for _, cid := range cids { + ctr, err := r.LookupContainer(cid) + if err != nil { + return nil, err + } + containers = append(containers, ctr) + } + return containers, nil +} 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/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 872c7bc26..c8be41636 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -365,6 +365,21 @@ func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error return call.ReplyStartContainer(ctr.ID()) } +// InitContainer initializes the container given by Varlink. +func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name, err.Error()) + } + if err := ctr.Init(getContext()); err != nil { + if errors.Cause(err) == libpod.ErrCtrStateInvalid { + return call.ReplyInvalidState(ctr.ID(), err.Error()) + } + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyInitContainer(ctr.ID()) +} + // StopContainer ... func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeout int64) error { ctr, err := i.Runtime.LookupContainer(name) 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/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/init_test.go b/test/e2e/init_test.go new file mode 100644 index 000000000..5865930a5 --- /dev/null +++ b/test/e2e/init_test.go @@ -0,0 +1,129 @@ +package integration + +import ( + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman init", 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 init bogus container", func() { + session := podmanTest.Podman([]string{"start", "123456"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) + + It("podman init with no arguments", func() { + session := podmanTest.Podman([]string{"start"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) + + It("podman init single container by ID", func() { + session := podmanTest.Podman([]string{"create", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + init := podmanTest.Podman([]string{"init", cid}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + }) + + It("podman init single container by name", func() { + name := "test1" + session := podmanTest.Podman([]string{"create", "--name", name, "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", name}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", name}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + }) + + It("podman init latest container", func() { + session := podmanTest.Podman([]string{"create", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", "--latest"}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", "--latest"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + }) + + It("podman init all three containers, one running", func() { + session := podmanTest.Podman([]string{"create", "--name", "test1", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session2 := podmanTest.Podman([]string{"create", "--name", "test2", "-d", ALPINE, "ls"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + session3 := podmanTest.Podman([]string{"run", "--name", "test3", "-d", ALPINE, "top"}) + session3.WaitWithDefaultTimeout() + Expect(session3.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", "--all"}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + result2 := podmanTest.Podman([]string{"inspect", "test2"}) + result2.WaitWithDefaultTimeout() + Expect(result2.ExitCode()).To(Equal(0)) + conData2 := result2.InspectContainerToJSON() + Expect(conData2[0].State.Status).To(Equal("created")) + result3 := podmanTest.Podman([]string{"inspect", "test3"}) + result3.WaitWithDefaultTimeout() + Expect(result3.ExitCode()).To(Equal(0)) + conData3 := result3.InspectContainerToJSON() + Expect(conData3[0].State.Status).To(Equal("running")) + }) + + It("podman init running container errors", func() { + session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", "--latest"}) + init.WaitWithDefaultTimeout() + Expect(init.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/vendor.conf b/vendor.conf index ace2298aa..c99b2c1d7 100644 --- a/vendor.conf +++ b/vendor.conf @@ -19,7 +19,7 @@ github.com/containers/image v1.5.1 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 -github.com/containers/storage v1.12.5 +github.com/containers/storage v1.12.6 github.com/containers/psgo v1.2.1 github.com/coreos/go-systemd v14 github.com/coreos/pkg v4 @@ -94,11 +94,11 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 github.com/varlink/go 64e07fabffa33e385817b41971cf2674f692f391 -github.com/containers/buildah 34e7eba408282e890e61395b6d97e58b88e14d25 +github.com/containers/buildah v1.8.1 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 github.com/fsouza/go-dockerclient v1.3.0 -github.com/openshift/imagebuilder 705fe9255c57f8505efb9723a9ac4082b67973bc +github.com/openshift/imagebuilder v1.1.0 github.com/ulikunitz/xz v0.5.5 github.com/coreos/go-iptables v0.4.0 github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index e29e69383..13526057c 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -26,7 +26,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.9.0-dev" + Version = "1.8.1" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index d9909cdc8..85848e297 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -1558,6 +1558,9 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image // stages. for i := range cleanupImages { removeID := cleanupImages[len(cleanupImages)-i-1] + if removeID == imageID { + continue + } if _, err := b.store.DeleteImage(removeID, true); err != nil { logrus.Debugf("failed to remove intermediate image %q: %v", removeID, err) if b.forceRmIntermediateCtrs || errors.Cause(err) != storage.ErrImageUsedByContainer { @@ -1663,6 +1666,7 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image if !b.layers { cleanupImages = append(cleanupImages, imageID) } + imageID = "" } } @@ -1812,9 +1816,10 @@ func (b *Executor) deleteSuccessfulIntermediateCtrs() error { } func (s *StageExecutor) EnsureContainerPath(path string) error { - _, err := os.Stat(filepath.Join(s.mountPoint, path)) + targetPath := filepath.Join(s.mountPoint, path) + _, err := os.Lstat(targetPath) if err != nil && os.IsNotExist(err) { - err = os.MkdirAll(filepath.Join(s.mountPoint, path), 0755) + err = os.MkdirAll(targetPath, 0755) } if err != nil { return errors.Wrapf(err, "error ensuring container path %q", path) diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index 7fa0a7777..e7a571db6 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -96,7 +96,7 @@ type FromAndBudResults struct { SecurityOpt []string ShmSize string Ulimit []string - Volume []string + Volumes []string } // GetUserNSFlags returns the common flags for usernamespace @@ -190,7 +190,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, fs.StringArrayVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])") fs.StringVar(&flags.ShmSize, "shm-size", "65536k", "size of '/dev/shm'. The format is `<number><unit>`.") fs.StringSliceVar(&flags.Ulimit, "ulimit", []string{}, "ulimit options (default [])") - fs.StringSliceVarP(&flags.Volume, "volume", "v", []string{}, "bind mount a volume into the container (default [])") + fs.StringSliceVarP(&flags.Volumes, "volume", "v", []string{}, "bind mount a volume into the container (default [])") // Add in the usernamespace and namespaceflags usernsFlags := GetUserNSFlags(usernsResults) diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index bec41f3ae..e8517eafb 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -149,27 +149,42 @@ func parseSecurityOpts(securityOpts []string, commonOpts *buildah.CommonBuildOpt return nil } +func ParseVolume(volume string) (specs.Mount, error) { + mount := specs.Mount{} + arr := strings.SplitN(volume, ":", 3) + if len(arr) < 2 { + return mount, errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume) + } + if err := validateVolumeHostDir(arr[0]); err != nil { + return mount, err + } + if err := validateVolumeCtrDir(arr[1]); err != nil { + return mount, err + } + mountOptions := "" + if len(arr) > 2 { + mountOptions = arr[2] + if err := validateVolumeOpts(arr[2]); err != nil { + return mount, err + } + } + mountOpts := strings.Split(mountOptions, ",") + mount.Source = arr[0] + mount.Destination = arr[1] + mount.Type = "rbind" + mount.Options = mountOpts + return mount, nil +} + // ParseVolumes validates the host and container paths passed in to the --volume flag func ParseVolumes(volumes []string) error { if len(volumes) == 0 { return nil } 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 { + if _, err := ParseVolume(volume); 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 } diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 8597e3656..1acf655eb 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -142,7 +142,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { g = nil logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd)) - if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil { + if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil && !os.IsExist(err) { return errors.Wrapf(err, "error ensuring working directory %q exists", spec.Process.Cwd) } diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 698d79a81..629d9748c 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -197,7 +197,7 @@ func FindImage(store storage.Store, firstRegistry string, systemContext *types.S break } if ref == nil || img == nil { - return nil, nil, errors.Wrapf(err, "error locating image with name %q", image) + return nil, nil, errors.Wrapf(err, "error locating image with name %q (%v)", image, names) } return ref, img, nil } diff --git a/vendor/github.com/containers/storage/drivers/driver.go b/vendor/github.com/containers/storage/drivers/driver.go index dda172574..e8f8bd5a7 100644 --- a/vendor/github.com/containers/storage/drivers/driver.go +++ b/vendor/github.com/containers/storage/drivers/driver.go @@ -40,6 +40,7 @@ var ( type CreateOpts struct { MountLabel string StorageOpt map[string]string + *idtools.IDMappings } // MountOpts contains optional arguments for LayerStope.Mount() methods. diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index 5d667d8c6..ef83b6c87 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -474,10 +474,22 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) { dir := d.dir(id) - rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) + uidMaps := d.uidMaps + gidMaps := d.gidMaps + + if opts != nil && opts.IDMappings != nil { + uidMaps = opts.IDMappings.UIDs() + gidMaps = opts.IDMappings.GIDs() + } + + rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) if err != nil { return err } + // Make the link directory if it does not exist + if err := idtools.MkdirAllAs(path.Join(d.home, linkDir), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { + return err + } if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil { return err } @@ -690,9 +702,17 @@ func (d *Driver) recreateSymlinks() error { if err != nil { return fmt.Errorf("error reading driver home directory %q: %v", d.home, err) } + // This makes the link directory if it doesn't exist + rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) + if err != nil { + return err + } + if err := idtools.MkdirAllAs(path.Join(d.home, linkDir), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { + return err + } for _, dir := range dirs { - // Skip over the linkDir - if dir.Name() == linkDir || dir.Mode().IsRegular() { + // Skip over the linkDir and anything that is not a directory + if dir.Name() == linkDir || !dir.Mode().IsDir() { continue } // Read the "link" file under each layer to get the name of the symlink diff --git a/vendor/github.com/containers/storage/drivers/vfs/driver.go b/vendor/github.com/containers/storage/drivers/vfs/driver.go index 5941ccc17..9e256858c 100644 --- a/vendor/github.com/containers/storage/drivers/vfs/driver.go +++ b/vendor/github.com/containers/storage/drivers/vfs/driver.go @@ -123,8 +123,13 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, ro bool return fmt.Errorf("--storage-opt is not supported for vfs") } + idMappings := d.idMappings + if opts != nil && opts.IDMappings != nil { + idMappings = opts.IDMappings + } + dir := d.dir(id) - rootIDs := d.idMappings.RootPair() + rootIDs := idMappings.RootPair() if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0700, rootIDs); err != nil { return err } diff --git a/vendor/github.com/containers/storage/layers.go b/vendor/github.com/containers/storage/layers.go index 110e737b2..7bec0aea6 100644 --- a/vendor/github.com/containers/storage/layers.go +++ b/vendor/github.com/containers/storage/layers.go @@ -614,6 +614,7 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab opts := drivers.CreateOpts{ MountLabel: mountLabel, StorageOpt: options, + IDMappings: idMappings, } if moreOptions.TemplateLayer != "" { if err = r.driver.CreateFromTemplate(id, moreOptions.TemplateLayer, templateIDMappings, parent, parentMappings, &opts, writeable); err != nil { diff --git a/vendor/github.com/openshift/imagebuilder/vendor.conf b/vendor/github.com/openshift/imagebuilder/vendor.conf index 39b216feb..e437b79c3 100644 --- a/vendor/github.com/openshift/imagebuilder/vendor.conf +++ b/vendor/github.com/openshift/imagebuilder/vendor.conf @@ -1,12 +1,11 @@ github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 -github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d +github.com/containers/storage v1.2 github.com/docker/docker b68221c37ee597950364788204546f9c9d0e46a1 github.com/docker/go-connections 97c2040d34dfae1d1b1275fa3a78dbdd2f41cf7e github.com/docker/go-units 2fb04c6466a548a03cb009c5569ee1ab1e35398e github.com/fsouza/go-dockerclient openshift-4.0 https://github.com/openshift/go-dockerclient.git github.com/gogo/protobuf c5a62797aee0054613cc578653a16c6237fef080 github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998 -github.com/golang/protobuf v1.3.0 github.com/konsorten/go-windows-terminal-sequences f55edac94c9bbba5d6182a4be46d86a2c9b5b50e github.com/Microsoft/go-winio 1a8911d1ed007260465c3bfbbc785ac6915a0bb8 github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 @@ -14,8 +13,8 @@ github.com/opencontainers/go-digest ac19fd6e7483ff933754af248d80be865e543d22 github.com/opencontainers/image-spec 243ea084a44451d27322fed02b682d99e2af3ba9 github.com/opencontainers/runc 923a8f8a9a07aceada5fc48c4d37e905d9b019b5 github.com/pkg/errors 27936f6d90f9c8e1145f11ed52ffffbfdb9e0af7 +github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac github.com/sirupsen/logrus d7b6bf5e4d26448fd977d07d745a2a66097ddecb golang.org/x/crypto ff983b9c42bc9fbf91556e191cc8efb585c16908 golang.org/x/net 45ffb0cd1ba084b73e26dee67e667e1be5acce83 -golang.org/x/sync 37e7f081c4d4c64e13b10787722085407fe5d15f golang.org/x/sys 7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba |