Skip to content

Commit

Permalink
Merge pull request containers#17656 from ygalblum/quadlet-container-m…
Browse files Browse the repository at this point in the history
…ount

Quadlet: Add support for the Mount key in .container files
  • Loading branch information
openshift-merge-robot authored Mar 1, 2023
2 parents 295dd26 + ccc5aa5 commit 3cab05a
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 15 deletions.
14 changes: 14 additions & 0 deletions docs/source/markdown/podman-systemd.unit.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Valid options for `[Container]` are listed below:
| Image=ubi8 | Image specification - ubi8 |
| Label="YXZ" | --label "XYZ" |
| LogDriver=journald | --log-driver journald |
| Mount=type=bind,source=/path/on/host,destination=/path/in/container | --mount type=bind,source=/path/on/host,destination=/path/in/container |
| Network=host | --net host |
| NoNewPrivileges=true | --security-opt no-new-privileges |
| Rootfs=/var/lib/rootfs | --rootfs /var/lib/rootfs |
Expand Down Expand Up @@ -217,6 +218,19 @@ Equivalent to the Podman `--log-driver` option.

The default value is `passthrough`.

### `Mount=`

Attach a filesystem mount to the container.
This is equivalent to the Podman `--mount` option, and
generally has the form `type=TYPE,TYPE-SPECIFIC-OPTION[,...]`.

As a special case, for `type=volume` if `source` ends with `.volume`, a Podman named volume called
`systemd-$name` will be used as the source, and the generated systemd service will contain
a dependency on the `$name-volume.service`. Such a volume can be automatically be lazily
created by using a `$name.volume` quadlet file.

This key can be listed multiple times.

### `Network=`

Specify a custom network for the container. This has the same format as the `--network` option
Expand Down
66 changes: 51 additions & 15 deletions pkg/systemd/quadlet/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
KeyImage = "Image"
KeyLabel = "Label"
KeyLogDriver = "LogDriver"
KeyMount = "Mount"
KeyNetwork = "Network"
KeyNetworkDisableDNS = "DisableDNS"
KeyNetworkDriver = "Driver"
Expand Down Expand Up @@ -106,6 +107,7 @@ var (
KeyImage: true,
KeyLabel: true,
KeyLogDriver: true,
KeyMount: true,
KeyNetwork: true,
KeyNoNewPrivileges: true,
KeyNotify: true,
Expand Down Expand Up @@ -464,21 +466,7 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
}

if source != "" {
if source[0] == '/' {
// Absolute path
service.Add(UnitGroup, "RequiresMountsFor", source)
} else if strings.HasSuffix(source, ".volume") {
// the podman volume name is systemd-$name
volumeName := replaceExtension(source, "", "systemd-", "")

// the systemd unit name is $name-volume.service
volumeServiceName := replaceExtension(source, ".service", "", "-volume")

source = volumeName

service.Add(UnitGroup, "Requires", volumeServiceName)
service.Add(UnitGroup, "After", volumeServiceName)
}
source = handleStorageSource(service, source)
}

podman.add("-v")
Expand Down Expand Up @@ -530,6 +518,34 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
podman.add("--secret", secret)
}

mounts := container.LookupAllArgs(ContainerGroup, KeyMount)
for _, mount := range mounts {
params := strings.Split(mount, ",")
paramsMap := make(map[string]string, len(params))
for _, param := range params {
kv := strings.Split(param, "=")
paramsMap[kv[0]] = kv[1]
}
if paramType, ok := paramsMap["type"]; ok {
if paramType == "volume" || paramType == "bind" {
if paramSource, ok := paramsMap["source"]; ok {
paramsMap["source"] = handleStorageSource(service, paramSource)
} else if paramSource, ok = paramsMap["src"]; ok {
paramsMap["src"] = handleStorageSource(service, paramSource)
}
}
}
paramsArray := make([]string, 0, len(params))
paramsArray = append(paramsArray, fmt.Sprintf("%s=%s", "type", paramsMap["type"]))
for k, v := range paramsMap {
if k != "type" {
paramsArray = append(paramsArray, fmt.Sprintf("%s=%s", k, v))
}
}
mountStr := strings.Join(paramsArray, ",")
podman.add("--mount", mountStr)
}

podmanArgs := container.LookupAllArgs(ContainerGroup, KeyPodmanArgs)
podman.add(podmanArgs...)

Expand Down Expand Up @@ -992,3 +1008,23 @@ func handleLogDriver(unitFile *parser.UnitFile, groupName string, podman *Podman
}
podman.add("--log-driver", logDriver)
}

func handleStorageSource(unitFile *parser.UnitFile, source string) string {
if source[0] == '/' {
// Absolute path
unitFile.Add(UnitGroup, "RequiresMountsFor", source)
} else if strings.HasSuffix(source, ".volume") {
// the podman volume name is systemd-$name
volumeName := replaceExtension(source, "", "systemd-", "")

// the systemd unit name is $name-volume.service
volumeServiceName := replaceExtension(source, ".service", "", "-volume")

source = volumeName

unitFile.Add(UnitGroup, "Requires", volumeServiceName)
unitFile.Add(UnitGroup, "After", volumeServiceName)
}

return source
}
20 changes: 20 additions & 0 deletions test/e2e/quadlet/mount.container
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[Container]
Image=localhost/imagename
## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,destination=/path/in/container"
Mount=type=bind,source=/path/on/host,destination=/path/in/container
## assert-podman-args-key-val "--mount" "," "type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared"
Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared
## assert-podman-args-key-val "--mount" "," "type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true"
Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true
## assert-podman-args-key-val "--mount" "," "type=volume,source=vol1,destination=/path/in/container,ro=true"
Mount=type=volume,source=vol1,destination=/path/in/container,ro=true
## assert-podman-args-key-val "--mount" "," "type=volume,source=systemd-vol2,destination=/path/in/container,ro=true"
## assert-key-is "Unit" "Requires" "vol2-volume.service"
## assert-key-is "Unit" "After" "vol2-volume.service"
Mount=type=volume,source=vol2.volume,destination=/path/in/container,ro=true
## assert-podman-args-key-val "--mount" "," "type=tmpfs,tmpfs-size=512M,destination=/path/in/container"
Mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container
## assert-podman-args-key-val "--mount" "," "type=image,source=fedora,destination=/fedora-image,rw=true"
Mount=type=image,source=fedora,destination=/fedora-image,rw=true
## assert-podman-args-key-val "--mount" "," "type=devpts,destination=/dev/pts"
Mount=type=devpts,destination=/dev/pts
46 changes: 46 additions & 0 deletions test/e2e/quadlet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"

Expand Down Expand Up @@ -175,6 +176,44 @@ func (t *quadletTestcase) assertPodmanArgsRegex(args []string, unit *parser.Unit
return findSublistRegex(podmanArgs, args) != -1
}

func keyValueStringToMap(keyValueString, separator string) map[string]string {
keyValMap := make(map[string]string)
keyVarList := strings.Split(keyValueString, separator)
for _, param := range keyVarList {
kv := strings.Split(param, "=")
keyValMap[kv[0]] = kv[1]
}

return keyValMap
}

func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.UnitFile, key string) bool {
podmanArgs, _ := unit.LookupLastArgs("Service", key)

expectedKeyValMap := keyValueStringToMap(args[2], args[1])
argKeyLocation := 0
for {
subListLocation := findSublist(podmanArgs[argKeyLocation:], []string{args[0]})
if subListLocation == -1 {
break
}

argKeyLocation += subListLocation
actualKeyValMap := keyValueStringToMap(podmanArgs[argKeyLocation+1], args[1])
if reflect.DeepEqual(expectedKeyValMap, actualKeyValMap) {
return true
}

argKeyLocation += 2

if argKeyLocation > len(podmanArgs) {
break
}
}

return false
}

func (t *quadletTestcase) assertPodmanFinalArgs(args []string, unit *parser.UnitFile, key string) bool {
podmanArgs, _ := unit.LookupLastArgs("Service", key)
if len(podmanArgs) < len(args) {
Expand All @@ -199,6 +238,10 @@ func (t *quadletTestcase) assertStartPodmanArgsRegex(args []string, unit *parser
return t.assertPodmanArgsRegex(args, unit, "ExecStart")
}

func (t *quadletTestcase) assertStartPodmanArgsKeyVal(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecStart")
}

func (t *quadletTestcase) assertStartPodmanFinalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgs(args, unit, "ExecStart")
}
Expand Down Expand Up @@ -264,6 +307,8 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
ok = t.assertStartPodmanArgs(args, unit)
case "assert-podman-args-regex":
ok = t.assertStartPodmanArgsRegex(args, unit)
case "assert-podman-args-key-val":
ok = t.assertStartPodmanArgsKeyVal(args, unit)
case "assert-podman-final-args":
ok = t.assertStartPodmanFinalArgs(args, unit)
case "assert-podman-final-args-regex":
Expand Down Expand Up @@ -484,6 +529,7 @@ var _ = Describe("quadlet system generator", func() {
Entry("env-host-false.container", "env-host-false.container"),
Entry("secrets.container", "secrets.container"),
Entry("logdriver.container", "logdriver.container"),
Entry("mount.container", "mount.container"),

Entry("basic.volume", "basic.volume"),
Entry("label.volume", "label.volume"),
Expand Down

0 comments on commit 3cab05a

Please sign in to comment.