diff options
30 files changed, 514 insertions, 67 deletions
@@ -107,6 +107,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func InspectPod(name: string) string](#InspectPod) +[func InspectVolume(name: string) string](#InspectVolume) + [func KillContainer(name: string, signal: int) string](#KillContainer) [func KillPod(name: string, signal: int) string](#KillPod) @@ -804,6 +806,12 @@ method InspectPod(name: [string](https://godoc.org/builtin#string)) [string](htt InspectPod takes the name or ID of an image and returns a string representation of data associated with the pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will be returned if the pod cannot be found. +### <a name="InspectVolume"></a>func InspectVolume +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method InspectVolume(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +InspectVolume inspects a single volume. Returns inspect JSON in the form of a +string. ### <a name="KillContainer"></a>func KillContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -454,7 +454,7 @@ uninstall: .PHONY: .gitvalidation .gitvalidation: .gopathok - GIT_CHECK_EXCLUDE="./vendor" $(GOBIN)/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..$(HEAD) + GIT_CHECK_EXCLUDE="./vendor:docs/rtd/make.bat" $(GOBIN)/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..$(HEAD) .PHONY: install.tools install.tools: .install.gitvalidation .install.gometalinter .install.md2man .install.ginkgo .install.golangci-lint ## Install needed tools diff --git a/cmd/podman/shared/volumes_shared.go b/cmd/podman/shared/volumes_shared.go index 912615cad..74c0ce011 100644 --- a/cmd/podman/shared/volumes_shared.go +++ b/cmd/podman/shared/volumes_shared.go @@ -2,8 +2,13 @@ package shared import ( "context" + "strconv" + "strings" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // Remove given set of volumes @@ -45,3 +50,60 @@ func SharedRemoveVolumes(ctx context.Context, runtime *libpod.Runtime, vols []st return success, failed, nil } + +// Handle volume options from CLI. +// Parse "o" option to find UID, GID. +func ParseVolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) { + libpodOptions := []libpod.VolumeCreateOption{} + volumeOptions := make(map[string]string) + + for key, value := range opts { + switch key { + case "o": + // o has special handling to parse out UID, GID. + // These are separate Libpod options. + splitVal := strings.Split(value, ",") + finalVal := []string{} + for _, o := range splitVal { + // Options will be formatted as either "opt" or + // "opt=value" + splitO := strings.SplitN(o, "=", 2) + switch strings.ToLower(splitO[0]) { + case "uid": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID") + } + intUID, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert UID %s to integer", splitO[1]) + } + logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID) + libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID)) + case "gid": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "gid option must provide a GID") + } + intGID, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert GID %s to integer", splitO[1]) + } + logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID) + libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID)) + default: + finalVal = append(finalVal, o) + } + } + if len(finalVal) > 0 { + volumeOptions[key] = strings.Join(finalVal, ",") + } + default: + volumeOptions[key] = value + } + } + + if len(volumeOptions) > 0 { + libpodOptions = append(libpodOptions, libpod.WithVolumeOptions(volumeOptions)) + } + + return libpodOptions, nil +} diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index 25514ec75..f8c476386 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -35,7 +35,7 @@ var ( statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." _statsCommand = &cobra.Command{ - Use: "stats [flags] CONTAINER [CONTAINER...]", + Use: "stats [flags] [CONTAINER...]", Short: "Display a live stream of container resource usage statistics", Long: statsDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -44,9 +44,6 @@ var ( statsCommand.Remote = remoteclient return statsCmd(&statsCommand) }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllAndLatest(cmd, args, false) - }, Example: `podman stats --all --no-stream podman stats ctrID podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, @@ -92,8 +89,6 @@ func statsCmd(c *cliconfig.StatsValues) error { if ctr > 1 { return errors.Errorf("--all, --latest and containers cannot be used together") - } else if ctr == 0 { - return errors.Errorf("you must specify --all, --latest, or at least one container") } runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 13e8394fb..dca366bc5 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -1268,6 +1268,10 @@ method VolumeRemove(options: VolumeRemoveOpts) -> (successes: []string, failures # GetVolumes gets slice of the volumes on a remote host method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) +# InspectVolume inspects a single volume. Returns inspect JSON in the form of a +# string. +method InspectVolume(name: string) -> (volume: string) + # VolumesPrune removes unused volumes on the host method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 617f701a4..e5a576749 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -37,7 +37,7 @@ func init() { flags := volumeCreateCommand.Flags() flags.StringVar(&volumeCreateCommand.Driver, "driver", "", "Specify volume driver name (default local)") flags.StringSliceVarP(&volumeCreateCommand.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") - flags.StringSliceVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") + flags.StringArrayVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") } func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 701f8b0fc..626ed9f64 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -800,7 +800,7 @@ Set the UTS mode for the container **ns**: specify the user namespace to use. Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. -**--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] +**--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman @@ -810,11 +810,23 @@ container. The `OPTIONS` are a comma delimited list and can be: * [z|Z] * [`[r]shared`|`[r]slave`|`[r]private`] -The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` -must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the -path you specify. For example, if you supply the `/foo` value, Podman creates a bind-mount. +The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume +will be mounted into the container at this directory. -You can specify multiple **-v** options to mount one or more mounts to a +Volumes may specify a source as well, as either a directory on the host or the +name of a named volume. If no source is given, the volume will be created as an +anonymous named volume with a randomly generated name, and will be removed when +the container is removed via the `--rm` flag or `podman rm --volumes`. + +If a volume source is specified, it must be a path on the host or the name of a +named volume. Host paths are allowed to be absolute or relative; relative paths +are resolved relative to the directory Podman is run in. Any source that does +not begin with a `.` or `/` it will be treated as the name of a named volume. +If a volume with that name does not exist, it will be created. Volumes created +with names are not anonymous and are not removed by `--rm` and +`podman rm --volumes`. + +You can specify multiple **-v** options to mount one or more volumes into a container. You can add `:ro` or `:rw` suffix to a volume to mount it read-only or diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 602aa69ed..9a92aba82 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -839,7 +839,7 @@ Set the UTS mode for the container **NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. -**--volume**, **-v**[=*[HOST-DIR-OR-VOUME-NAME:CONTAINER-DIR[:OPTIONS]]*] +**--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman @@ -853,11 +853,23 @@ create one. * [`z`|`Z`] * [`[r]shared`|`[r]slave`|`[r]private`] -The `/CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `/HOST-DIR` -must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the -path you specify. For example, if you supply the `/foo` value, Podman creates a bind-mount. +The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume +will be mounted into the container at this directory. -You can specify multiple **-v** options to mount one or more mounts to a +Volumes may specify a source as well, as either a directory on the host or the +name of a named volume. If no source is given, the volume will be created as an +anonymous named volume with a randomly generated name, and will be removed when +the container is removed via the `--rm` flag or `podman rm --volumes`. + +If a volume source is specified, it must be a path on the host or the name of a +named volume. Host paths are allowed to be absolute or relative; relative paths +are resolved relative to the directory Podman is run in. Any source that does +not begin with a `.` or `/` it will be treated as the name of a named volume. +If a volume with that name does not exist, it will be created. Volumes created +with names are not anonymous and are not removed by `--rm` and +`podman rm --volumes`. + +You can specify multiple **-v** options to mount one or more volumes into a container. You can add `:ro` or `:rw` suffix to a volume to mount it read-only or diff --git a/docs/rtd/Makefile b/docs/rtd/Makefile new file mode 100644 index 000000000..d0c3cbf10 --- /dev/null +++ b/docs/rtd/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/rtd/make.bat b/docs/rtd/make.bat new file mode 100644 index 000000000..6247f7e23 --- /dev/null +++ b/docs/rtd/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/rtd/requirements.txt b/docs/rtd/requirements.txt new file mode 100644 index 000000000..44af373ac --- /dev/null +++ b/docs/rtd/requirements.txt @@ -0,0 +1,4 @@ +# requirements file for readthedocs pip installs + +# use md instead of rst +recommonmark diff --git a/docs/rtd/source/conf.py b/docs/rtd/source/conf.py new file mode 100644 index 000000000..9290c343a --- /dev/null +++ b/docs/rtd/source/conf.py @@ -0,0 +1,56 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Podman' +copyright = '2019, team' +author = 'team' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'recommonmark', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Extension configuration ------------------------------------------------- diff --git a/docs/rtd/source/index.rst b/docs/rtd/source/index.rst new file mode 100644 index 000000000..90033056c --- /dev/null +++ b/docs/rtd/source/index.rst @@ -0,0 +1,20 @@ +.. Podman documentation master file, created by + sphinx-quickstart on Tue Oct 22 15:20:30 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Podman's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/tutorials/rootless_tutorial.md b/docs/tutorials/rootless_tutorial.md index ed700485a..9a31826bd 100644 --- a/docs/tutorials/rootless_tutorial.md +++ b/docs/tutorials/rootless_tutorial.md @@ -13,7 +13,7 @@ The alternative OCI runtime support for cgroup V2 can be turned on at the comma ``` sudo podman --runtime /usr/bin/crun ``` -or by changing the value for the "Default OCI runtime" in the libpod.conf file either at the system level or at the [#user-configuration-files](user level) from `runtime = "runc"` to `runtime = "crun"`. +or by changing the value for the "Default OCI runtime" in the libpod.conf file either at the system level or at the [user level](#user-configuration-files) from `runtime = "runc"` to `runtime = "crun"`. ## Administrator Actions @@ -59,7 +59,7 @@ The format of this file is USERNAME:UID:RANGE This means the user johndoe is allocated UIDS 100000-165535 as well as their standard UID in the /etc/passwd file. NOTE: this is not currently supported with network installs. These files must be available locally to the host machine. It is not possible to configure this with LDAP or Active Directory. -If you update either the /etc/subuid or the /etc/subgid file, you need to stop all the running containers owned by the user and kill the pause process that is running on the system for that user. This can be done automatically by using the `[podman system migrate](https://github.com/containers/libpod/blob/master/docs/podman-system-migrate.1.md)` command which will stop all the containers for the user and will kill the pause process. +If you update either the /etc/subuid or the /etc/subgid file, you need to stop all the running containers owned by the user and kill the pause process that is running on the system for that user. This can be done automatically by using the [`podman system migrate`](https://github.com/containers/libpod/blob/master/docs/podman-system-migrate.1.md) command which will stop all the containers for the user and will kill the pause process. Rather than updating the files directly, the usermod program can be used to assign UIDs and GIDs to a user. diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 658a2fe4e..448e05bdf 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -602,7 +602,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options if err != nil { return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) } - if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe, sessionID); err != nil { + if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { return -1, nil, err } @@ -986,7 +986,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if err != nil { return err } - if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe, ctr.ID()); err != nil { + if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil { return err } /* Wait for initial setup and fork, and reap child */ @@ -1213,7 +1213,7 @@ func startCommandGivenSelinux(cmd *exec.Cmd) error { // moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup // it then signals for conmon to start by sending nonse data down the start fd -func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File, uuid string) error { +func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File) error { mustCreateCgroup := true // If cgroup creation is disabled - just signal. if ctr.config.NoCgroups { diff --git a/libpod/options.go b/libpod/options.go index ddc5993af..17a075d2d 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1487,6 +1487,8 @@ func WithVolumeLabels(labels map[string]string) VolumeCreateOption { } // WithVolumeOptions sets the options of the volume. +// If the "local" driver has been selected, options will be validated. There are +// currently 3 valid options for the "local" driver - o, type, and device. func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { @@ -1495,6 +1497,13 @@ func WithVolumeOptions(options map[string]string) VolumeCreateOption { volume.config.Options = make(map[string]string) for key, value := range options { + switch key { + case "type", "device", "o": + volume.config.Options[key] = value + default: + return errors.Wrapf(define.ErrInvalidArg, "unrecognized volume option %q is not supported with local driver", key) + } + volume.config.Options[key] = value } diff --git a/libpod/runtime.go b/libpod/runtime.go index 74c47317f..a06b2bb51 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -509,6 +509,17 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. return nil, err } + // storage.conf + storageConfFile, err := storage.DefaultConfigFile(rootless.IsRootless()) + if err != nil { + return nil, err + } + + createStorageConfFile := false + if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + createStorageConfFile = true + } + defRunConf, err := defaultRuntimeConfig() if err != nil { return nil, err @@ -683,27 +694,21 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. } if rootless.IsRootless() && configPath == "" { - configPath, err := getRootlessConfigPath() - if err != nil { - return nil, err - } - - // storage.conf - storageConfFile, err := storage.DefaultConfigFile(rootless.IsRootless()) - if err != nil { - return nil, err - } - if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + if createStorageConfFile { if err := util.WriteStorageConfigFile(&runtime.config.StorageConfig, storageConfFile); err != nil { return nil, errors.Wrapf(err, "cannot write config file %s", storageConfFile) } } + configPath, err := getRootlessConfigPath() + if err != nil { + return nil, err + } if configPath != "" { - if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(configPath), 0711); err != nil { return nil, err } - file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil && !os.IsExist(err) { return nil, errors.Wrapf(err, "cannot open file %s", configPath) } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 411264d25..2b214d572 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -295,21 +295,32 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai // Maintain an array of them - we need to lock them later. ctrNamedVolumes := make([]*Volume, 0, len(ctr.config.NamedVolumes)) for _, vol := range ctr.config.NamedVolumes { - // Check if it exists already - dbVol, err := r.state.Volume(vol.Name) - if err == nil { - ctrNamedVolumes = append(ctrNamedVolumes, dbVol) - // The volume exists, we're good - continue - } else if errors.Cause(err) != define.ErrNoSuchVolume { - return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + isAnonymous := false + if vol.Name == "" { + // Anonymous volume. We'll need to create it. + // It needs a name first. + vol.Name = stringid.GenerateNonCryptoID() + isAnonymous = true + } else { + // Check if it exists already + dbVol, err := r.state.Volume(vol.Name) + if err == nil { + ctrNamedVolumes = append(ctrNamedVolumes, dbVol) + // The volume exists, we're good + continue + } else if errors.Cause(err) != define.ErrNoSuchVolume { + return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + } } logrus.Debugf("Creating new volume %s for container", vol.Name) // The volume does not exist, so we need to create it. - newVol, err := r.newVolume(ctx, WithVolumeName(vol.Name), withSetCtrSpecific(), - WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) + volOptions := []VolumeCreateOption{WithVolumeName(vol.Name), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())} + if isAnonymous { + volOptions = append(volOptions, withSetCtrSpecific()) + } + newVol, err := r.newVolume(ctx, volOptions...) if err != nil { return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) } diff --git a/libpod/volume_inspect.go b/libpod/volume_inspect.go index 87ed9d340..c333b8961 100644 --- a/libpod/volume_inspect.go +++ b/libpod/volume_inspect.go @@ -62,6 +62,9 @@ func (v *Volume) Inspect() (*InspectVolumeData, error) { } data.Scope = v.Scope() data.Options = make(map[string]string) + for k, v := range v.config.Options { + data.Options[k] = v + } data.UID = v.config.UID data.GID = v.config.GID data.ContainerSpecific = v.config.IsCtrSpecific diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 5c33467a7..bff93cc9e 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -437,7 +437,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode } if c.IsSet("rm") { - if err := r.Runtime.RemoveContainer(ctx, ctr, false, false); err != nil { + if err := r.Runtime.RemoveContainer(ctx, ctr, false, true); err != nil { logrus.Errorf("Error removing container %s: %v", ctr.ID(), err) } } @@ -1053,7 +1053,7 @@ func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.Cle // Only used when cleaning up containers func removeContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error { - if err := runtime.RemoveContainer(ctx, ctr, false, false); err != nil { + if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil { return errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) } return nil diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 0706d4b6a..84d43c337 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -186,7 +186,12 @@ func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCrea } if len(opts) != 0 { - options = append(options, libpod.WithVolumeOptions(opts)) + // We need to process -o for uid, gid + parsedOptions, err := shared.ParseVolumeOptions(opts) + if err != nil { + return "", err + } + options = append(options, parsedOptions...) } newVolume, err := r.NewVolume(ctx, options...) if err != nil { diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 3b808a2ee..870e86896 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -659,12 +659,39 @@ func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestM } // InspectVolumes returns a slice of volumes based on an arg list or --all -func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) { - reply, err := iopodman.GetVolumes().Call(r.Conn, c.InputArgs, c.All) - if err != nil { - return nil, err +func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*libpod.InspectVolumeData, error) { + var ( + inspectData []*libpod.InspectVolumeData + volumes []string + ) + + if c.All { + allVolumes, err := r.Volumes(ctx) + if err != nil { + return nil, err + } + for _, vol := range allVolumes { + volumes = append(volumes, vol.Name()) + } + } else { + for _, arg := range c.InputArgs { + volumes = append(volumes, arg) + } } - return varlinkVolumeToVolume(r, reply), nil + + for _, vol := range volumes { + jsonString, err := iopodman.InspectVolume().Call(r.Conn, vol) + if err != nil { + return nil, err + } + inspectJSON := new(libpod.InspectVolumeData) + if err := json.Unmarshal([]byte(jsonString), inspectJSON); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling inspect JSON for volume %s", vol) + } + inspectData = append(inspectData, inspectJSON) + } + + return inspectData, nil } // Volumes returns a slice of adapter.volumes based on information about libpod diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 93919dd0a..095534589 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -11,7 +11,6 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/util" pmount "github.com/containers/storage/pkg/mount" - "github.com/containers/storage/pkg/stringid" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -648,7 +647,7 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string 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]") + volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") for _, vol := range config.Volumes { var ( @@ -665,7 +664,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string src = splitVol[0] if len(splitVol) == 1 { - dest = src + // This is an anonymous named volume. Only thing given + // is destination. + // Name/source will be blank, and populated by libpod. + src = "" + dest = splitVol[0] } else if len(splitVol) > 1 { dest = splitVol[1] } @@ -675,8 +678,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string } } - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, err + // Do not check source dir for anonymous volumes + if len(splitVol) > 1 { + if err := parse.ValidateVolumeHostDir(src); err != nil { + return nil, nil, err + } } if err := parse.ValidateVolumeCtrDir(dest); err != nil { return nil, nil, err @@ -732,13 +738,13 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string Destination: cleanDest, Source: TypeTmpfs, Type: TypeTmpfs, - Options: []string{"rprivate", "rw", "nodev"}, + Options: []string{"rprivate", "rw", "nodev", "exec"}, } mounts[vol] = mount } else { + // Anonymous volumes have no name. namedVolume := new(libpod.ContainerNamedVolume) - namedVolume.Name = stringid.GenerateNonCryptoID() - namedVolume.Options = []string{"rprivate", "rw", "nodev"} + namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} namedVolume.Dest = cleanDest volumes[vol] = namedVolume } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 0190b106d..d9a84e4e5 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -318,7 +318,7 @@ func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf strin if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { return err } - storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_TRUNC, 0600) if err != nil { return errors.Wrapf(err, "cannot open %s", storageConf) } diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index b41eb5086..2dddd3008 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -3,6 +3,8 @@ package varlinkapi import ( + "encoding/json" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" @@ -22,7 +24,11 @@ func (i *LibpodAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.Vol volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(options.Labels)) } if len(options.Options) > 0 { - volumeOptions = append(volumeOptions, libpod.WithVolumeOptions(options.Options)) + parsedOptions, err := shared.ParseVolumeOptions(options.Options) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + volumeOptions = append(volumeOptions, parsedOptions...) } newVolume, err := i.Runtime.NewVolume(getContext(), volumeOptions...) if err != nil { @@ -80,6 +86,23 @@ func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all boo return call.ReplyGetVolumes(volumes) } +// InspectVolume inspects a single volume, returning its JSON as a string. +func (i *LibpodAPI) InspectVolume(call iopodman.VarlinkCall, name string) error { + vol, err := i.Runtime.LookupVolume(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + inspectOut, err := vol.Inspect() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + inspectJSON, err := json.Marshal(inspectOut) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyInspectVolume(string(inspectJSON)) +} + // VolumesPrune removes unused images via a varlink call func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { var errs []string diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 94bfebab7..c96059787 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -280,4 +280,88 @@ var _ = Describe("Podman run with volumes", func() { session2.WaitWithDefaultTimeout() Expect(session2.ExitCode()).To(Equal(0)) }) + + It("podman run with anonymous volume", func() { + list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list1.WaitWithDefaultTimeout() + Expect(list1.ExitCode()).To(Equal(0)) + Expect(list1.OutputToString()).To(Equal("")) + + session := podmanTest.Podman([]string{"create", "-v", "/test", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + list2 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list2.WaitWithDefaultTimeout() + Expect(list2.ExitCode()).To(Equal(0)) + arr := list2.OutputToStringArray() + Expect(len(arr)).To(Equal(1)) + Expect(arr[0]).To(Not(Equal(""))) + }) + + It("podman rm -v removes anonymous volume", func() { + list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list1.WaitWithDefaultTimeout() + Expect(list1.ExitCode()).To(Equal(0)) + Expect(list1.OutputToString()).To(Equal("")) + + ctrName := "testctr" + session := podmanTest.Podman([]string{"create", "--name", ctrName, "-v", "/test", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + list2 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list2.WaitWithDefaultTimeout() + Expect(list2.ExitCode()).To(Equal(0)) + arr := list2.OutputToStringArray() + Expect(len(arr)).To(Equal(1)) + Expect(arr[0]).To(Not(Equal(""))) + + remove := podmanTest.Podman([]string{"rm", "-v", ctrName}) + remove.WaitWithDefaultTimeout() + Expect(remove.ExitCode()).To(Equal(0)) + + list3 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list3.WaitWithDefaultTimeout() + Expect(list3.ExitCode()).To(Equal(0)) + Expect(list3.OutputToString()).To(Equal("")) + }) + + It("podman rm -v retains named volume", func() { + list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list1.WaitWithDefaultTimeout() + Expect(list1.ExitCode()).To(Equal(0)) + Expect(list1.OutputToString()).To(Equal("")) + + ctrName := "testctr" + volName := "testvol" + session := podmanTest.Podman([]string{"create", "--name", ctrName, "-v", fmt.Sprintf("%s:/test", volName), ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + list2 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list2.WaitWithDefaultTimeout() + Expect(list2.ExitCode()).To(Equal(0)) + arr := list2.OutputToStringArray() + Expect(len(arr)).To(Equal(1)) + Expect(arr[0]).To(Equal(volName)) + + remove := podmanTest.Podman([]string{"rm", "-v", ctrName}) + remove.WaitWithDefaultTimeout() + Expect(remove.ExitCode()).To(Equal(0)) + + list3 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list3.WaitWithDefaultTimeout() + Expect(list3.ExitCode()).To(Equal(0)) + arr2 := list3.OutputToStringArray() + Expect(len(arr2)).To(Equal(1)) + Expect(arr2[0]).To(Equal(volName)) + }) + + It("podman run image volume is not noexec", func() { + session := podmanTest.Podman([]string{"run", "--rm", redis, "grep", "/data", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Not(ContainSubstring("noexec"))) + }) }) diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index 4000ab33a..fbf7c9920 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -11,6 +11,8 @@ import ( . "github.com/onsi/gomega" ) +// TODO: we need to check the output. Currently, we only check the exit codes +// which is not enough. var _ = Describe("Podman stats", func() { var ( tempdir string @@ -61,6 +63,15 @@ var _ = Describe("Podman stats", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman stats on all running containers", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"stats", "--no-stream"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman stats only output cids", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 41107b5ba..71023f9e2 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -63,4 +64,23 @@ var _ = Describe("Podman volume create", func() { session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) }) + + It("podman create volume with o=uid,gid", func() { + volName := "testVol" + uid := "3000" + gid := "4000" + session := podmanTest.Podman([]string{"volume", "create", "--opt", fmt.Sprintf("o=uid=%s,gid=%s", uid, gid), volName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .UID }}", volName}) + inspectUID.WaitWithDefaultTimeout() + Expect(inspectUID.ExitCode()).To(Equal(0)) + Expect(inspectUID.OutputToString()).To(Equal(uid)) + + inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .GID }}", volName}) + inspectGID.WaitWithDefaultTimeout() + Expect(inspectGID.ExitCode()).To(Equal(0)) + Expect(inspectGID.OutputToString()).To(Equal(gid)) + }) }) diff --git a/test/e2e/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index 0683c6bbf..5015e0535 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -2,6 +2,7 @@ package integration import ( "os" + "strings" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -74,4 +75,16 @@ var _ = Describe("Podman volume inspect", func() { Expect(session.OutputToStringArray()[0]).To(Equal(volName1)) Expect(session.OutputToStringArray()[1]).To(Equal(volName2)) }) + + It("inspect volume finds options", func() { + volName := "testvol" + session := podmanTest.Podman([]string{"volume", "create", "--opt", "type=tmpfs", volName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"volume", "inspect", volName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(strings.Contains(inspect.OutputToString(), "tmpfs")).To(BeTrue()) + }) }) diff --git a/test/system/015-help.bats b/test/system/015-help.bats index a987f04bc..fd4be87b2 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -55,9 +55,11 @@ function check_help() { # If usage has required arguments, try running without them if expr "$usage" : '.*\[flags\] [A-Z]' >/dev/null; then - dprint "podman $@ $cmd (without required args)" - run_podman 125 "$@" $cmd - is "$output" "Error:" + if [ "$cmd" != "stats"]; then + dprint "podman $@ $cmd (without required args)" + run_podman 125 "$@" $cmd + is "$output" "Error:" + fi fi count=$(expr $count + 1) |