Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quadlet: Add support for the Mount key in .container files #17656

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]))
ygalblum marked this conversation as resolved.
Show resolved Hide resolved
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