diff options
-rw-r--r-- | cmd/podman/common.go | 5 | ||||
-rw-r--r-- | cmd/podman/create.go | 42 | ||||
-rw-r--r-- | cmd/podman/run.go | 3 | ||||
-rw-r--r-- | cmd/podman/spec.go | 24 | ||||
-rw-r--r-- | cmd/podman/spec_test.go | 2 | ||||
-rw-r--r-- | completions/bash/podman | 2 | ||||
-rw-r--r-- | docs/podman-create.1.md | 8 | ||||
-rw-r--r-- | docs/podman-run.1.md | 8 | ||||
-rw-r--r-- | libpod/container_api.go | 7 | ||||
-rw-r--r-- | libpod/container_internal.go | 45 | ||||
-rw-r--r-- | libpod/util.go | 13 |
11 files changed, 141 insertions, 18 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 657535e63..8dee6cb98 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -197,6 +197,11 @@ var createFlags = []cli.Flag{ Name: "hostname", Usage: "Set container hostname", }, + cli.StringFlag{ + Name: "image-volume, builtin-volume", + Usage: "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", + Value: "bind", + }, cli.BoolFlag{ Name: "interactive, i", Usage: "Keep STDIN open even if not attached", diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 3d811b58b..810a5e3ed 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -89,6 +89,8 @@ type createConfig struct { Hostname string //hostname Image string ImageID string + BuiltinImgVolumes map[string]struct{} // volumes defined in the image config + ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore Interactive bool //interactive IpcMode container.IpcMode //ipc IP6Address string //ipv6 @@ -180,6 +182,7 @@ func createCmd(c *cli.Context) error { if err != nil { return err } + useImageVolumes := createConfig.ImageVolumeType == "bind" runtimeSpec, err := createConfigToOCISpec(createConfig) if err != nil { @@ -190,7 +193,7 @@ func createCmd(c *cli.Context) error { return errors.Wrapf(err, "unable to parse new container options") } // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true)) + options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, useImageVolumes)) options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) options = append(options, libpod.WithLabels(createConfig.Labels)) options = append(options, libpod.WithUser(createConfig.User)) @@ -626,19 +629,32 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") } + ImageVolumes := data.ContainerConfig.Volumes + + var imageVolType = map[string]string{ + "bind": "", + "tmpfs": "", + "ignore": "", + } + if _, ok := imageVolType[c.String("image-volume")]; !ok { + return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) + } + config := &createConfig{ - Runtime: runtime, - CapAdd: c.StringSlice("cap-add"), - CapDrop: c.StringSlice("cap-drop"), - CgroupParent: c.String("cgroup-parent"), - Command: command, - Detach: c.Bool("detach"), - Devices: c.StringSlice("device"), - DNSOpt: c.StringSlice("dns-opt"), - DNSSearch: c.StringSlice("dns-search"), - DNSServers: c.StringSlice("dns"), - Entrypoint: entrypoint, - Env: env, + Runtime: runtime, + BuiltinImgVolumes: ImageVolumes, + ImageVolumeType: c.String("image-volume"), + CapAdd: c.StringSlice("cap-add"), + CapDrop: c.StringSlice("cap-drop"), + CgroupParent: c.String("cgroup-parent"), + Command: command, + Detach: c.Bool("detach"), + Devices: c.StringSlice("device"), + DNSOpt: c.StringSlice("dns-opt"), + DNSSearch: c.StringSlice("dns-search"), + DNSServers: c.StringSlice("dns"), + Entrypoint: entrypoint, + Env: env, //ExposedPorts: ports, GroupAdd: groupAdd, Hostname: c.String("hostname"), diff --git a/cmd/podman/run.go b/cmd/podman/run.go index f13e293bc..3d6175cef 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -54,6 +54,7 @@ func runCmd(c *cli.Context) error { if err != nil { return err } + useImageVolumes := createConfig.ImageVolumeType == "bind" runtimeSpec, err := createConfigToOCISpec(createConfig) if err != nil { @@ -66,7 +67,7 @@ func runCmd(c *cli.Context) error { } // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true)) + options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, useImageVolumes)) options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) options = append(options, libpod.WithLabels(createConfig.Labels)) options = append(options, libpod.WithUser(createConfig.User)) diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index e78118b2f..2c2005399 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -351,7 +351,7 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { } // BIND MOUNTS - mounts, err := config.GetVolumeMounts() + mounts, err := config.GetVolumeMounts(configSpec.Mounts) if err != nil { return nil, errors.Wrapf(err, "error getting volume mounts") } @@ -500,7 +500,7 @@ func getDefaultAnnotations() map[string]string { } //GetVolumeMounts takes user provided input for bind mounts and creates Mount structs -func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { +func (c *createConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { var m []spec.Mount var options []string for _, i := range c.Volumes { @@ -509,6 +509,9 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { if len(spliti) > 2 { options = strings.Split(spliti[2], ",") } + if libpod.MountExists(specMounts, spliti[1]) { + continue + } options = append(options, "rbind") var foundrw, foundro, foundz, foundZ bool var rootProp string @@ -550,6 +553,23 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { Options: options, }) } + + // volumes from image config + if c.ImageVolumeType != "tmpfs" { + return m, nil + } + for vol := range c.BuiltinImgVolumes { + if libpod.MountExists(specMounts, vol) { + continue + } + mount := spec.Mount{ + Destination: vol, + Type: string(TypeTmpfs), + Source: string(TypeTmpfs), + Options: []string{"rw", "noexec", "nosuid", "nodev", "tmpcopyup"}, + } + m = append(m, mount) + } return m, nil } diff --git a/cmd/podman/spec_test.go b/cmd/podman/spec_test.go index 768b079aa..1e1064df9 100644 --- a/cmd/podman/spec_test.go +++ b/cmd/podman/spec_test.go @@ -18,7 +18,7 @@ func TestCreateConfig_GetVolumeMounts(t *testing.T) { config := createConfig{ Volumes: []string{"foobar:/foobar:ro"}, } - specMount, err := config.GetVolumeMounts() + specMount, err := config.GetVolumeMounts([]spec.Mount{}) assert.NoError(t, err) assert.True(t, reflect.DeepEqual(data, specMount[0])) } diff --git a/completions/bash/podman b/completions/bash/podman index 905792a9b..6d9098fc9 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1042,6 +1042,7 @@ _podman_container_run() { --attach -a --blkio-weight --blkio-weight-device + --builtin-volume --cap-add --cap-drop --cgroup-parent @@ -1068,6 +1069,7 @@ _podman_container_run() { --expose --group-add --hostname -h + --image-volume --init-path --ip --ip6 diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 7d2b62fb0..98141221b 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -217,6 +217,14 @@ inside of the container. **--help** Print usage statement +**--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore* + Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind'). + bind: A directory is created inside the container state directory and bind mounted into + the container for the volumes. + tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create + content that dissapears when the container is stopped. + ignore: All volumes are just ignored and no action is taken. + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 53239422d..5e235514c 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -214,6 +214,14 @@ inside of the container. **--help** Print usage statement +**--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore* + Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind') + bind: A directory is created inside the container state directory and bind mounted into + the container for the volumes. + tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create + content that dissapears when the container is stopped. + ignore: All volumes are just ignored and no action is taken. + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/libpod/container_api.go b/libpod/container_api.go index 6fdc45589..149197470 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -144,6 +144,13 @@ func (c *Container) Init() (err error) { } g.AddMount(hostnameMnt) + // Bind builtin image volumes + if c.config.ImageVolumes { + if err = c.addImageVolumes(&g); err != nil { + return errors.Wrapf(err, "error mounting image volumes") + } + } + if c.config.User != "" { if !c.state.Mounted { return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 6e9852d1e..e22d36f99 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -12,10 +12,12 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/chrootarchive" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/stringid" spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -271,6 +273,49 @@ func (c *Container) export(path string) error { return err } +func (c *Container) addImageVolumes(g *generate.Generator) error { + mountPoint := c.state.Mountpoint + if !c.state.Mounted { + return errors.Wrapf(ErrInternal, "container is not mounted") + } + + imageStorage, err := c.runtime.getImage(c.config.RootfsImageID) + if err != nil { + return err + } + imageData, err := c.runtime.getImageInspectInfo(*imageStorage) + if err != nil { + return err + } + + for k := range imageData.ContainerConfig.Volumes { + mount := spec.Mount{ + Destination: k, + Type: "bind", + Options: []string{"rbind", "rw"}, + } + if MountExists(g.Mounts(), k) { + continue + } + volumePath := filepath.Join(c.config.StaticDir, "volumes", k) + if _, err := os.Stat(volumePath); os.IsNotExist(err) { + if err = os.MkdirAll(volumePath, 0755); err != nil { + return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID) + } + if err = label.Relabel(volumePath, c.config.MountLabel, false); err != nil { + return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, k, c.ID) + } + srcPath := filepath.Join(mountPoint, k) + if err = chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, k, c.ID, srcPath) + } + mount.Source = volumePath + } + g.AddMount(mount) + } + return nil +} + // Get path of artifact with a given name for this container func (c *Container) getArtifactPath(name string) string { return filepath.Join(c.config.StaticDir, artifactsDir, name) diff --git a/libpod/util.go b/libpod/util.go index 1a033a940..0c6700fbf 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -4,13 +4,14 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "time" "github.com/containers/image/signature" "github.com/containers/image/types" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "strconv" ) // Runtime API constants @@ -96,3 +97,13 @@ func RemoveScientificNotationFromFloat(x float64) (float64, error) { } return result, nil } + +// MountExists returns true if dest exists in the list of mounts +func MountExists(specMounts []spec.Mount, dest string) bool { + for _, m := range specMounts { + if m.Destination == dest { + return true + } + } + return false +} |