diff options
-rw-r--r-- | cmd/podman/common.go | 4 | ||||
-rw-r--r-- | cmd/podman/create.go | 15 | ||||
-rw-r--r-- | completions/bash/podman | 1 | ||||
-rw-r--r-- | docs/podman-create.1.md | 13 | ||||
-rw-r--r-- | docs/podman-run.1.md | 13 | ||||
-rw-r--r-- | libpod/container_internal.go | 4 | ||||
-rw-r--r-- | libpod/image/image_test.go | 2 | ||||
-rw-r--r-- | libpod/image/parts.go | 25 | ||||
-rw-r--r-- | libpod/image/parts_test.go | 6 | ||||
-rw-r--r-- | libpod/image/pull_test.go | 12 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 7 | ||||
-rw-r--r-- | pkg/spec/spec.go | 43 |
12 files changed, 124 insertions, 21 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 93b16f4e3..43a0fe061 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -383,6 +383,10 @@ var createFlags = []cli.Flag{ Name: "sysctl", Usage: "Sysctl options (default [])", }, + cli.BoolTFlag{ + Name: "systemd", + Usage: "Run container in systemd mode if the command executable is systemd or init", + }, cli.StringSliceFlag{ Name: "tmpfs", Usage: "Mount a temporary filesystem (`tmpfs`) into a container (default [])", diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 209064eff..e7e349306 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "strconv" "strings" "syscall" @@ -509,7 +510,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // STOP SIGNAL stopSignal := syscall.SIGTERM - signalString := "SIGTERM" + signalString := "" if data != nil { signalString = data.ContainerConfig.StopSignal } @@ -648,6 +649,17 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) } + var systemd bool + if c.BoolT("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { + systemd = true + if signalString == "" { + stopSignal, err = signal.ParseSignal("RTMIN+3") + if err != nil { + return nil, errors.Wrapf(err, "error parsing systemd signal") + } + } + } + config := &cc.CreateConfig{ Runtime: runtime, Annotations: annotations, @@ -726,6 +738,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim StopSignal: stopSignal, StopTimeout: c.Uint("stop-timeout"), Sysctl: sysctl, + Systemd: systemd, Tmpfs: c.StringSlice("tmpfs"), Tty: tty, User: user, diff --git a/completions/bash/podman b/completions/bash/podman index 083211fc1..91c08a78c 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1494,6 +1494,7 @@ _podman_container_run() { --subgidname --subuidname --sysctl + --systemd --uidmap --ulimit --user -u diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index f27a84b1d..8523d0c78 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -544,6 +544,19 @@ Network Namespace - current sysctls allowed: Note: if you use the --network=host option these sysctls will not be allowed. +**--systemd**=*true*|*false* + +Run container in systemd mode. The default is *true*. + +If the command you running inside of the container is systemd or init, podman +will setup tmpfs mount points in the following directories: + +/run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal + +It will also set the default stop signal to SIGRTMIN+3. + +This allow systemd to run in a confined container without any modifications. + **--tmpfs**=[] Create a tmpfs mount Mount a temporary filesystem (`tmpfs`) mount into a container, for example: diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 7b23f356f..59b9d4b01 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -578,6 +578,19 @@ Network Namespace - current sysctls allowed: Note: if you use the `--network=host` option these sysctls will not be allowed. +**--systemd**=*true*|*false* + +Run container in systemd mode. The default is *true*. + +If the command you running inside of the container is systemd or init, podman +will setup tmpfs mount points in the following directories: + +/run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal + +It will also set the default stop signal to SIGRTMIN+3. + +This allow systemd to run in a confined container without any modifications. + **--tmpfs**=[] Create a tmpfs mount Mount a temporary filesystem (`tmpfs`) mount into a container, for example: diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 1c6143cd2..79bc49c37 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1295,6 +1295,10 @@ func (c *Container) mount() (string, error) { if err != nil { return "", errors.Wrapf(err, "error mounting storage for container %s", c.ID()) } + mountPoint, err = filepath.EvalSymlinks(mountPoint) + if err != nil { + return "", errors.Wrapf(err, "error resolving storage path for container %s", c.ID()) + } return mountPoint, nil } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 0aa637dab..f187631b4 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -221,7 +221,7 @@ func TestNormalizeTag(t *testing.T) { {"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix + ":none"}, // Qualified name@digest; FIXME: The result is not even syntactically valid! {"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest {"busybox:latest", "localhost/busybox:latest"}, // Unqualified name-only - {"ns/busybox:latest", "ns/busybox:latest"}, // Unqualified with a dot-less namespace FIXME: "ns" is treated as a registry + {"ns/busybox:latest", "localhost/ns/busybox:latest"}, // Unqualified with a dot-less namespace } { res, err := normalizeTag(c.input) if c.expected == "" { diff --git a/libpod/image/parts.go b/libpod/image/parts.go index 127f723a8..1509005e5 100644 --- a/libpod/image/parts.go +++ b/libpod/image/parts.go @@ -2,6 +2,7 @@ package image import ( "fmt" + "strings" "github.com/containers/image/docker/reference" ) @@ -16,6 +17,11 @@ type imageParts struct { hasRegistry bool } +// Registries must contain a ":" or a "." or be localhost +func isRegistry(name string) bool { + return strings.ContainsAny(name, ".:") || name == "localhost" +} + // decompose breaks an input name into an imageParts description func decompose(input string) (imageParts, error) { var ( @@ -37,10 +43,16 @@ func decompose(input string) (imageParts, error) { tag = ntag.Tag() } registry := reference.Domain(imgRef.(reference.Named)) - if registry != "" { + imageName := reference.Path(imgRef.(reference.Named)) + // Is this a registry or a repo? + if isRegistry(registry) { hasRegistry = true + } else { + if registry != "" { + imageName = registry + "/" + imageName + registry = "" + } } - imageName := reference.Path(imgRef.(reference.Named)) return imageParts{ registry: registry, hasRegistry: hasRegistry, @@ -53,10 +65,15 @@ func decompose(input string) (imageParts, error) { // assemble concatenates an image's parts into a string func (ip *imageParts) assemble() string { - return fmt.Sprintf("%s/%s:%s", ip.registry, ip.name, ip.tag) + spec := fmt.Sprintf("%s:%s", ip.name, ip.tag) + + if ip.registry != "" { + spec = fmt.Sprintf("%s/%s", ip.registry, spec) + } + return spec } // assemble concatenates an image's parts with transport into a string func (ip *imageParts) assembleWithTransport() string { - return fmt.Sprintf("%s%s/%s:%s", ip.transport, ip.registry, ip.name, ip.tag) + return fmt.Sprintf("%s%s", ip.transport, ip.assemble()) } diff --git a/libpod/image/parts_test.go b/libpod/image/parts_test.go index eba938cc2..518538f0b 100644 --- a/libpod/image/parts_test.go +++ b/libpod/image/parts_test.go @@ -27,12 +27,10 @@ func TestDecompose(t *testing.T) { }, { // Unqualified single-name input "busybox", "docker://", "", "busybox", "latest", false, false, - // FIXME? The [empty]/busybox syntax is surprising. - "/busybox:latest", "docker:///busybox:latest", + "busybox:latest", "docker://busybox:latest", }, { // Unqualified namespaced input - // FIXME: .registry == "ns" !! - "ns/busybox", "docker://", "ns", "busybox", "latest", false, true, + "ns/busybox", "docker://", "", "ns/busybox", "latest", false, false, "ns/busybox:latest", "docker://ns/busybox:latest", }, { // name:tag diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go index 5ef8c47a5..37b45dc83 100644 --- a/libpod/image/pull_test.go +++ b/libpod/image/pull_test.go @@ -98,9 +98,8 @@ func TestGetPullRefPair(t *testing.T) { "example.com/from-directory" + digestSuffix, "example.com/from-directory" + digestSuffix, }, { // ns/name:tag, no registry: - // FIXME: This is interpreted as "registry == ns" "dir:/dev/this-does-not-exist", "ns/from-directory:notlatest", - "ns/from-directory:notlatest", "docker.io/ns/from-directory:notlatest", + "localhost/ns/from-directory:notlatest", "localhost/ns/from-directory:notlatest", }, { // containers-storage image ID "dir:/dev/this-does-not-exist", imageID, @@ -218,9 +217,8 @@ func TestPullGoalFromImageReference(t *testing.T) { false, }, { // Relative path, multiple elements. - // FIXME: This does not add localhost/, so dstName is normalized to docker.io/testdata. "dir:testdata/this-does-not-exist", - []expected{{"testdata/this-does-not-exist", "docker.io/testdata/this-does-not-exist:latest"}}, + []expected{{"localhost/testdata/this-does-not-exist:latest", "localhost/testdata/this-does-not-exist:latest"}}, false, }, @@ -343,12 +341,10 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { }, { // Unqualified, namespaced, name-only "ns/busybox", - // FIXME: This is interpreted as "registry == ns", and actual pull happens from docker.io/ns/busybox:latest; - // example.com should be first in the list but isn't used at all. []pullRefStrings{ - {"ns/busybox", "docker://ns/busybox:latest", "docker.io/ns/busybox:latest"}, + {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, }, - false, + true, }, { // Unqualified, name:tag "busybox:notlatest", diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index c7eaeb9f7..486281200 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -115,9 +115,10 @@ type CreateConfig struct { Resources CreateResourceConfig Rm bool //rm ShmDir string - StopSignal syscall.Signal // stop-signal - StopTimeout uint // stop-timeout - Sysctl map[string]string //sysctl + StopSignal syscall.Signal // stop-signal + StopTimeout uint // stop-timeout + Sysctl map[string]string //sysctl + Systemd bool Tmpfs []string // tmpfs Tty bool //tty UsernsMode container.UsernsMode //userns diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 26b93f5fe..11bc880cb 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -4,6 +4,7 @@ import ( "os" "strings" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/daemon/caps" "github.com/docker/docker/pkg/mount" @@ -221,6 +222,12 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } } + if config.Systemd && (strings.HasSuffix(config.Command[0], "init") || + strings.HasSuffix(config.Command[0], "systemd")) { + if err := setupSystemd(config, &g); err != nil { + return nil, errors.Wrap(err, "failed to setup systemd") + } + } for _, i := range config.Tmpfs { // Default options if nothing passed options := []string{"rw", "private", "noexec", "nosuid", "nodev", "size=65536k"} @@ -353,6 +360,42 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) } } +// systemd expects to have /run, /run/lock and /tmp on tmpfs +// It also expects to be able to write to /sys/fs/cgroup/systemd and /var/log/journal + +func setupSystemd(config *CreateConfig, g *generate.Generator) error { + mounts, err := config.GetVolumeMounts([]spec.Mount{}) + if err != nil { + return err + } + options := []string{"rw", "private", "noexec", "nosuid", "nodev"} + for _, dest := range []string{"/run", "/run/lock", "/sys/fs/cgroup/systemd"} { + if libpod.MountExists(mounts, dest) { + continue + } + tmpfsMnt := spec.Mount{ + Destination: dest, + Type: "tmpfs", + Source: "tmpfs", + Options: append(options, "tmpcopyup", "size=65536k"), + } + g.AddMount(tmpfsMnt) + } + for _, dest := range []string{"/tmp", "/var/log/journal"} { + if libpod.MountExists(mounts, dest) { + continue + } + tmpfsMnt := spec.Mount{ + Destination: dest, + Type: "tmpfs", + Source: "tmpfs", + Options: append(options, "tmpcopyup"), + } + g.AddMount(tmpfsMnt) + } + return nil +} + func addPidNS(config *CreateConfig, g *generate.Generator) error { pidMode := config.PidMode if IsNS(string(pidMode)) { |