diff --git a/cmd/quadlet/main.go b/cmd/quadlet/main.go index b36997b32a..491d8eb3cb 100644 --- a/cmd/quadlet/main.go +++ b/cmd/quadlet/main.go @@ -54,6 +54,7 @@ var ( ".kube": 3, ".network": 2, ".image": 1, + ".pod": 4, } ) @@ -389,6 +390,23 @@ func warnIfAmbiguousName(unit *parser.UnitFile, group string) { } } +func generatePodsInfoMap(units []*parser.UnitFile) map[string]*quadlet.PodInfo { + podsInfoMap := make(map[string]*quadlet.PodInfo) + for _, unit := range units { + if !strings.HasSuffix(unit.Filename, ".pod") { + continue + } + + serviceName := quadlet.GetPodServiceName(unit) + podsInfoMap[unit.Filename] = &quadlet.PodInfo{ + ServiceName: serviceName, + Containers: make([]string, 0), + } + } + + return podsInfoMap +} + func main() { if err := process(); err != nil { Logf("%s", err.Error()) @@ -478,6 +496,9 @@ func process() error { return getOrder(i) < getOrder(j) }) + // Generate the PodsInfoMap to allow containers to link to their pods and add themselves to the pod's containers list + podsInfoMap := generatePodsInfoMap(units) + // A map of network/volume unit file-names, against their calculated names, as needed by Podman. var resourceNames = make(map[string]string) @@ -489,7 +510,7 @@ func process() error { switch { case strings.HasSuffix(unit.Filename, ".container"): warnIfAmbiguousName(unit, quadlet.ContainerGroup) - service, err = quadlet.ConvertContainer(unit, resourceNames, isUserFlag) + service, err = quadlet.ConvertContainer(unit, resourceNames, isUserFlag, podsInfoMap) case strings.HasSuffix(unit.Filename, ".volume"): warnIfAmbiguousName(unit, quadlet.VolumeGroup) service, name, err = quadlet.ConvertVolume(unit, unit.Filename, resourceNames) @@ -500,6 +521,8 @@ func process() error { case strings.HasSuffix(unit.Filename, ".image"): warnIfAmbiguousName(unit, quadlet.ImageGroup) service, name, err = quadlet.ConvertImage(unit) + case strings.HasSuffix(unit.Filename, ".pod"): + service, err = quadlet.ConvertPod(unit, unit.Filename, podsInfoMap) default: Logf("Unsupported file type %q", unit.Filename) continue diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md index 8101338a56..a2744105a8 100644 --- a/docs/source/markdown/podman-systemd.unit.5.md +++ b/docs/source/markdown/podman-systemd.unit.5.md @@ -6,7 +6,7 @@ podman\-systemd.unit - systemd units using Podman Quadlet ## SYNOPSIS -*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image +*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image, *name*.pod ### Podman unit search path @@ -35,13 +35,11 @@ the [Service] table and [Install] tables pass directly to systemd and are handle See systemd.unit(5) man page for more information. The Podman generator reads the search paths above and reads files with the extensions `.container` -`.volume` and `*.kube`, and for each file generates a similarly named `.service` file. Be aware that +`.volume`, `.network`, `.pod` and `.kube`, and for each file generates a similarly named `.service` file. Be aware that existing vendor services (i.e., in `/usr/`) are replaced if they have the same name. The generated unit files can be started and managed with `systemctl` like any other systemd service. `systemctl {--user} list-unit-files` lists existing unit files on the system. -Files with the `.network` extension are only read if they are mentioned in a `.container` file. See the `Network=` key. - The Podman files use the same format as [regular systemd unit files](https://www.freedesktop.org/software/systemd/man/systemd.syntax.html). Each file type has a custom section (for example, `[Container]`) that is handled by Podman, and all other sections are passed on untouched, allowing the use of any normal systemd configuration options @@ -72,7 +70,8 @@ Quadlet requires the use of cgroup v2, use `podman info --format {{.Host.Cgroups ### Service Type By default, the `Type` field of the `Service` section of the Quadlet file does not need to be set. -Quadlet will set it to `notify` for `.container` and `.kube` files and to `oneshot` for `.volume`, `.network` and `.image` files. +Quadlet will set it to `notify` for `.container` and `.kube` files, +`forking` for `.pod` files, and `oneshot` for `.volume`, `.network` and `.image` files. However, `Type` may be explicitly set to `oneshot` for `.container` and `.kube` files when no containers are expected to run once `podman` exits. @@ -190,6 +189,7 @@ Valid options for `[Container]` are listed below: | Rootfs=/var/lib/rootfs | --rootfs /var/lib/rootfs | | Notify=true | --sdnotify container | | PidsLimit=10000 | --pids-limit 10000 | +| Pod=pod-name | --pod=pod-name | | PodmanArgs=--add-host foobar | --add-host foobar | | PublishPort=50-59 | --publish 50-59 | | Pull=never | --pull=never | @@ -505,6 +505,14 @@ setting up a container healthcheck, see the `HealthCmd` option for more. Tune the container's pids limit. This is equivalent to the Podman `--pids-limit` option. +### `Pod=` + +Specify a Quadlet `.pod` unit to link the container to. +The value must take the form of `.pod` and the `.pod` unit must exist. + +Quadlet will add all the necessary parameters to link between the container and the pod and between their corresponding services. + + ### `PodmanArgs=` This key contains a list of arguments passed directly to the end of the `podman run` command @@ -662,6 +670,69 @@ Working directory inside the container. The default working directory for running binaries within a container is the root directory (/). The image developer can set a different default with the WORKDIR instruction. This option overrides the working directory by using the -w option. +## Pod units [Pod] + +Pod units are named with a `.pod` extension and contain a `[Pod]` section describing +the pod that is created and run as a service. The resulting service file contains a line like +`ExecStartPre=podman pod create …`, and most of the keys in this section control the command-line +options passed to Podman. + +By default, the Podman pod has the same name as the unit, but with a `systemd-` prefix, i.e. +a `$name.pod` file creates a `$name-pod.service` unit and a `systemd-$name` Podman pod. The +`PodName` option allows for overriding this default name with a user-provided one. + +Valid options for `[Container]` are listed below: + +| **[Pod] options** | **podman container create equivalent** | +|-------------------------------------|----------------------------------------| +| ContainersConfModule=/etc/nvd\.conf | --module=/etc/nvd\.conf | +| GlobalArgs=--log-level=debug | --log-level=debug | +| PodmanArgs=\-\-cpus=2 | --cpus=2 | +| PodName=name | --name=name | + +Supported keys in the `[Pod]` section are: + +### `ContainersConfModule=` + +Load the specified containers.conf(5) module. Equivalent to the Podman `--module` option. + +This key can be listed multiple times. + +### `GlobalArgs=` + +This key contains a list of arguments passed directly between `podman` and `kube` +in the generated file (right before the image name in the command line). It can be used to +access Podman features otherwise unsupported by the generator. Since the generator is unaware +of what unexpected interactions can be caused by these arguments, it is not recommended to use +this option. + +The format of this is a space separated list of arguments, which can optionally be individually +escaped to allow inclusion of whitespace and other control characters. + +This key can be listed multiple times. + +### `PodmanArgs=` + +This key contains a list of arguments passed directly to the end of the `podman kube play` command +in the generated file (right before the path to the yaml file in the command line). It can be used to +access Podman features otherwise unsupported by the generator. Since the generator is unaware +of what unexpected interactions can be caused by these arguments, is not recommended to use +this option. + +The format of this is a space separated list of arguments, which can optionally be individually +escaped to allow inclusion of whitespace and other control characters. + +This key can be listed multiple times. + +### `PodName=` + +The (optional) name of the Podman pod. If this is not specified, the default value +of `systemd-%N` is used, which is the same as the service name but with a `systemd-` +prefix to avoid conflicts with user-managed containers. + +Please note that pods and containers cannot have the same name. +So, if PodName is set, it must not conflict with any container. + ## Kube units [Kube] Kube units are named with a `.kube` extension and contain a `[Kube]` section describing @@ -1299,6 +1370,22 @@ IPRange=172.16.0.0/28 Label=org.test.Key=value ``` +Example for Container in a Pod: + +`test.pod` +``` +[Pod] +PodName=test +``` + +`centos.container` +``` +[Container] +Image=quay.io/centos/centos:latest +Exec=sh -c "sleep inf" +Pod=test.pod +``` + ## SEE ALSO **[systemd.unit(5)](https://www.freedesktop.org/software/systemd/man/systemd.unit.html)**, **[systemd.service(5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html)**, diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index 3328087900..59f2e56e08 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -29,6 +29,7 @@ const ( InstallGroup = "Install" KubeGroup = "Kube" NetworkGroup = "Network" + PodGroup = "Pod" ServiceGroup = "Service" UnitGroup = "Unit" VolumeGroup = "Volume" @@ -36,6 +37,7 @@ const ( XContainerGroup = "X-Container" XKubeGroup = "X-Kube" XNetworkGroup = "X-Network" + XPodGroup = "X-Pod" XVolumeGroup = "X-Volume" XImageGroup = "X-Image" ) @@ -114,6 +116,8 @@ const ( KeyOS = "OS" KeyPidsLimit = "PidsLimit" KeyPodmanArgs = "PodmanArgs" + KeyPodName = "PodName" + KeyPod = "Pod" KeyPublishPort = "PublishPort" KeyPull = "Pull" KeyReadOnly = "ReadOnly" @@ -153,6 +157,11 @@ const ( KeyYaml = "Yaml" ) +type PodInfo struct { + ServiceName string + Containers []string +} + var ( validPortRange = regexp.Delayed(`\d+(-\d+)?(/udp|/tcp)?$`) @@ -199,6 +208,7 @@ var ( KeyNoNewPrivileges: true, KeyNotify: true, KeyPidsLimit: true, + KeyPod: true, KeyPodmanArgs: true, KeyPublishPort: true, KeyPull: true, @@ -307,6 +317,13 @@ var ( KeyTLSVerify: true, KeyVariant: true, } + + supportedPodKeys = map[string]bool{ + KeyContainersConfModule: true, + KeyGlobalArgs: true, + KeyPodmanArgs: true, + KeyPodName: true, + } ) func replaceExtension(name string, extension string, extraPrefix string, extraSuffix string) string { @@ -382,7 +399,7 @@ func usernsOpts(kind string, opts []string) string { // service file (unit file with Service group) based on the options in the // Container group. // The original Container group is kept around as X-Container. -func ConvertContainer(container *parser.UnitFile, names map[string]string, isUser bool) (*parser.UnitFile, error) { +func ConvertContainer(container *parser.UnitFile, names map[string]string, isUser bool, podsInfoMap map[string]*PodInfo) (*parser.UnitFile, error) { service := container.Dup() service.Filename = replaceExtension(container.Filename, ".service", "", "") @@ -770,6 +787,10 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse podman.add("--pull", pull) } + if err := handlePod(container, service, ContainerGroup, podsInfoMap, podman); err != nil { + return nil, err + } + handlePodmanArgs(container, ContainerGroup, podman) if len(image) > 0 { @@ -1228,6 +1249,95 @@ func ConvertImage(image *parser.UnitFile) (*parser.UnitFile, string, error) { return service, imageName, nil } +func GetPodServiceName(podUnit *parser.UnitFile) string { + return replaceExtension(podUnit.Filename, "", "", "-pod") +} + +func ConvertPod(podUnit *parser.UnitFile, name string, podsInfoMap map[string]*PodInfo) (*parser.UnitFile, error) { + podInfo, ok := podsInfoMap[podUnit.Filename] + if !ok { + return nil, fmt.Errorf("internal error while processing pod %s", podUnit.Filename) + } + + service := podUnit.Dup() + service.Filename = replaceExtension(podInfo.ServiceName, ".service", "", "") + + if podUnit.Path != "" { + service.Add(UnitGroup, "SourcePath", podUnit.Path) + } + + if err := checkForUnknownKeys(podUnit, PodGroup, supportedPodKeys); err != nil { + return nil, err + } + + // Derive pod name from unit name (with added prefix), or use user-provided name. + podName, ok := podUnit.Lookup(PodGroup, KeyPodName) + if !ok || len(podName) == 0 { + podName = replaceExtension(name, "", "systemd-", "") + } + + /* Rename old Pod group to x-Pod so that systemd ignores it */ + service.RenameGroup(PodGroup, XPodGroup) + + // Need the containers filesystem mounted to start podman + service.Add(UnitGroup, "RequiresMountsFor", "%t/containers") + + for _, containerService := range podInfo.Containers { + service.Add(UnitGroup, "Wants", containerService) + service.Add(UnitGroup, "Before", containerService) + } + + if !podUnit.HasKey(ServiceGroup, "SyslogIdentifier") { + service.Set(ServiceGroup, "SyslogIdentifier", "%N") + } + + execStart := createBasePodmanCommand(podUnit, PodGroup) + execStart.add("pod", "start", "--pod-id-file=%t/%N.pod-id") + service.AddCmdline(ServiceGroup, "ExecStart", execStart.Args) + + execStop := createBasePodmanCommand(podUnit, PodGroup) + execStop.add("pod", "stop") + execStop.add( + "--pod-id-file=%t/%N.pod-id", + "--ignore", + "--time=10", + ) + service.AddCmdline(ServiceGroup, "ExecStop", execStop.Args) + + execStopPost := createBasePodmanCommand(podUnit, PodGroup) + execStopPost.add("pod", "rm") + execStopPost.add( + "--pod-id-file=%t/%N.pod-id", + "--ignore", + "--force", + ) + service.AddCmdline(ServiceGroup, "ExecStopPost", execStopPost.Args) + + execStartPre := createBasePodmanCommand(podUnit, PodGroup) + execStartPre.add("pod", "create") + execStartPre.add( + "--infra-conmon-pidfile=%t/%N.pid", + "--pod-id-file=%t/%N.pod-id", + "--exit-policy=stop", + "--replace", + ) + + execStartPre.addf("--name=%s", podName) + + handlePodmanArgs(podUnit, PodGroup, execStartPre) + + service.AddCmdline(ServiceGroup, "ExecStartPre", execStartPre.Args) + + service.Setv(ServiceGroup, + "Environment", "PODMAN_SYSTEMD_UNIT=%n", + "Type", "forking", + "Restart", "on-failure", + "PIDFile", "%t/%N.pid", + ) + + return service, nil +} + func handleUser(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline) error { user, hasUser := unitFile.Lookup(groupName, KeyUser) okUser := hasUser && len(user) > 0 @@ -1688,3 +1798,26 @@ func createBasePodmanCommand(unitFile *parser.UnitFile, groupName string) *Podma return podman } + +func handlePod(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupName string, podsInfoMap map[string]*PodInfo, podman *PodmanCmdline) error { + pod, ok := quadletUnitFile.Lookup(groupName, KeyPod) + if ok && len(pod) > 0 { + if !strings.HasSuffix(pod, ".pod") { + return fmt.Errorf("pod %s is not Quadlet based", pod) + } + + podInfo, ok := podsInfoMap[pod] + if !ok { + return fmt.Errorf("quadlet pod unit %s does not exist", pod) + } + + podman.add("--pod-id-file", fmt.Sprintf("%%t/%s.pod-id", podInfo.ServiceName)) + + podServiceName := fmt.Sprintf("%s.service", podInfo.ServiceName) + serviceUnitFile.Add(UnitGroup, "BindsTo", podServiceName) + serviceUnitFile.Add(UnitGroup, "After", podServiceName) + + podInfo.Containers = append(podInfo.Containers, serviceUnitFile.Filename) + } + return nil +} diff --git a/test/e2e/quadlet/basic.pod b/test/e2e/quadlet/basic.pod new file mode 100644 index 0000000000..2b8a7bb1e4 --- /dev/null +++ b/test/e2e/quadlet/basic.pod @@ -0,0 +1,9 @@ +## assert-key-is Unit RequiresMountsFor "%t/containers" +## assert-key-is Service Type forking +## assert-key-is Service SyslogIdentifier "%N" +## assert-key-is-regex Service ExecStartPre ".*/podman pod create --infra-conmon-pidfile=%t/%N.pid --pod-id-file=%t/%N.pod-id --exit-policy=stop --replace --name=systemd-basic" +## assert-key-is-regex Service ExecStart ".*/podman pod start --pod-id-file=%t/%N.pod-id" +## assert-key-is-regex Service ExecStop ".*/podman pod stop --pod-id-file=%t/%N.pod-id --ignore --time=10" +## assert-key-is-regex Service ExecStopPost ".*/podman pod rm --pod-id-file=%t/%N.pod-id --ignore --force" + +[Pod] diff --git a/test/e2e/quadlet/name.pod b/test/e2e/quadlet/name.pod new file mode 100644 index 0000000000..33c528d7bc --- /dev/null +++ b/test/e2e/quadlet/name.pod @@ -0,0 +1,4 @@ +## assert-podman-pre-args "--name=test-pod" + +[Pod] +PodName=test-pod diff --git a/test/e2e/quadlet/pod.non-quadlet.container b/test/e2e/quadlet/pod.non-quadlet.container new file mode 100644 index 0000000000..cde0459fb9 --- /dev/null +++ b/test/e2e/quadlet/pod.non-quadlet.container @@ -0,0 +1,6 @@ +## assert-failed +## assert-stderr-contains "pod test-pod is not Quadlet based" + +[Container] +Image=localhost/imagename +Pod=test-pod diff --git a/test/e2e/quadlet/pod.not-found.container b/test/e2e/quadlet/pod.not-found.container new file mode 100644 index 0000000000..13c64fefe1 --- /dev/null +++ b/test/e2e/quadlet/pod.not-found.container @@ -0,0 +1,6 @@ +## assert-failed +## assert-stderr-contains "quadlet pod unit not-found.pod does not exist" + +[Container] +Image=localhost/imagename +Pod=not-found.pod diff --git a/test/e2e/quadlet/podmanargs.pod b/test/e2e/quadlet/podmanargs.pod new file mode 100644 index 0000000000..4e35532fb7 --- /dev/null +++ b/test/e2e/quadlet/podmanargs.pod @@ -0,0 +1,13 @@ +## assert-podman-pre-args "--foo" +## assert-podman-pre-args "--bar" +## assert-podman-pre-args "--also" +## assert-podman-pre-args "--with-key=value" +## assert-podman-pre-args "--with-space" "yes" + + +[Pod] +PodmanArgs="--foo" \ + --bar +PodmanArgs=--also +PodmanArgs=--with-key=value +PodmanArgs=--with-space yes diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index ad3061f4cd..161aae6507 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -39,6 +39,8 @@ func loadQuadletTestcase(path string) *quadletTestcase { service += "-network" case ".image": service += "-image" + case ".pod": + service += "-pod" } service += ".service" @@ -331,6 +333,46 @@ func (t *quadletTestcase) assertStartPodmanFinalArgsRegex(args []string, unit *p return t.assertPodmanFinalArgsRegex(args, unit, "ExecStart") } +func (t *quadletTestcase) assertStartPrePodmanArgs(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgs(args, unit, "ExecStartPre", false, false) +} + +func (t *quadletTestcase) assertStartPrePodmanArgsRegex(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgs(args, unit, "ExecStartPre", true, false) +} + +func (t *quadletTestcase) assertStartPrePodmanGlobalArgs(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgs(args, unit, "ExecStartPre", false, true) +} + +func (t *quadletTestcase) assertStartPrePodmanGlobalArgsRegex(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgs(args, unit, "ExecStartPre", true, true) +} + +func (t *quadletTestcase) assertStartPrePodmanArgsKeyVal(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", false, false) +} + +func (t *quadletTestcase) assertStartPrePodmanArgsKeyValRegex(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", true, false) +} + +func (t *quadletTestcase) assertStartPrePodmanGlobalArgsKeyVal(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", false, true) +} + +func (t *quadletTestcase) assertStartPrePodmanGlobalArgsKeyValRegex(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", true, true) +} + +func (t *quadletTestcase) assertStartPrePodmanFinalArgs(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanFinalArgs(args, unit, "ExecStartPre") +} + +func (t *quadletTestcase) assertStartPrePodmanFinalArgsRegex(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanFinalArgsRegex(args, unit, "ExecStartPre") +} + func (t *quadletTestcase) assertStopPodmanArgs(args []string, unit *parser.UnitFile) bool { return t.assertPodmanArgs(args, unit, "ExecStop", false, false) } @@ -440,6 +482,26 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio ok = t.assertStartPodmanFinalArgs(args, unit) case "assert-podman-final-args-regex": ok = t.assertStartPodmanFinalArgsRegex(args, unit) + case "assert-podman-pre-args": + ok = t.assertStartPrePodmanArgs(args, unit) + case "assert-podman-pre-args-regex": + ok = t.assertStartPrePodmanArgsRegex(args, unit) + case "assert-podman-pre-args-key-val": + ok = t.assertStartPrePodmanArgsKeyVal(args, unit) + case "assert-podman-pre-args-key-val-regex": + ok = t.assertStartPrePodmanArgsKeyValRegex(args, unit) + case "assert-podman-pre-global-args": + ok = t.assertStartPrePodmanGlobalArgs(args, unit) + case "assert-podman-pre-global-args-regex": + ok = t.assertStartPrePodmanGlobalArgsRegex(args, unit) + case "assert-podman-pre-global-args-key-val": + ok = t.assertStartPrePodmanGlobalArgsKeyVal(args, unit) + case "assert-podman-pre-global-args-key-val-regex": + ok = t.assertStartPrePodmanGlobalArgsKeyValRegex(args, unit) + case "assert-podman-pre-final-args": + ok = t.assertStartPrePodmanFinalArgs(args, unit) + case "assert-podman-pre-final-args-regex": + ok = t.assertStartPrePodmanFinalArgsRegex(args, unit) case "assert-symlink": ok = t.assertSymlink(args, unit) case "assert-podman-stop-args": @@ -715,6 +777,8 @@ BOGUS=foo Entry("notify-healthy.container", "notify-healthy.container", 0, ""), Entry("oneshot.container", "oneshot.container", 0, ""), Entry("other-sections.container", "other-sections.container", 0, ""), + Entry("pod.non-quadlet.container", "pod.non-quadlet.container", 1, "converting \"pod.non-quadlet.container\": pod test-pod is not Quadlet based"), + Entry("pod.not-found.container", "pod.not-found.container", 1, "converting \"pod.not-found.container\": quadlet pod unit not-found.pod does not exist"), Entry("podmanargs.container", "podmanargs.container", 0, ""), Entry("ports.container", "ports.container", 0, ""), Entry("ports_ipv6.container", "ports_ipv6.container", 0, ""), @@ -822,6 +886,10 @@ BOGUS=foo Entry("Image - Arch and OS", "arch-os.image", 0, ""), Entry("Image - global args", "globalargs.image", 0, ""), Entry("Image - Containers Conf Modules", "containersconfmodule.image", 0, ""), + + Entry("basic.pod", "basic.pod", 0, ""), + Entry("name.pod", "name.pod", 0, ""), + Entry("podmanargs.pod", "podmanargs.pod", 0, ""), ) }) diff --git a/test/system/252-quadlet.bats b/test/system/252-quadlet.bats index 2b57e58edd..6780af194f 100644 --- a/test/system/252-quadlet.bats +++ b/test/system/252-quadlet.bats @@ -1412,4 +1412,62 @@ EOF run_podman rmi --ignore $(pause_image) } +@test "quadlet - pod simple" { + local quadlet_tmpdir=$PODMAN_TMPDIR/quadlets + + local test_pod_name=pod_test_$(random_string) + local quadlet_pod_unit=$test_pod_name.pod + local quadlet_pod_file=$PODMAN_TMPDIR/$quadlet_pod_unit + cat > $quadlet_pod_file < $quadlet_container_file <