Skip to content

Commit

Permalink
Quadlet: Add support for the Mount key in .container files
Browse files Browse the repository at this point in the history
Handle the Mount key
Reuse code from the handling of the Volume key
Add E2E Test
E2E Test - Add checker for KeyValue string
Update man page

Signed-off-by: Ygal Blum <[email protected]>
  • Loading branch information
ygalblum committed Feb 28, 2023
1 parent a54a404 commit ccc5aa5
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 ccc5aa5

Please sign in to comment.