mirror of
https://github.com/containers/podman.git
synced 2025-10-15 18:23:30 +08:00
Quadlet - Add support for .pod units
Add support for .pod unit files with only PodmanArgs, GlobalArgs, ContainersConfModule and PodName Add support for linking .container units with .pod ones Add e2e and system tests Add to man page Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
@ -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
|
||||
|
@ -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 |
|
||||
@ -501,6 +501,14 @@ of startup on its own.
|
||||
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 `<name>.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
|
||||
@ -658,6 +666,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
|
||||
@ -1295,6 +1366,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)**,
|
||||
|
@ -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", "", "")
|
||||
|
||||
@ -767,6 +784,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 {
|
||||
@ -1225,6 +1246,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
|
||||
@ -1685,3 +1795,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
|
||||
}
|
||||
|
9
test/e2e/quadlet/basic.pod
Normal file
9
test/e2e/quadlet/basic.pod
Normal file
@ -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]
|
4
test/e2e/quadlet/name.pod
Normal file
4
test/e2e/quadlet/name.pod
Normal file
@ -0,0 +1,4 @@
|
||||
## assert-podman-pre-args "--name=test-pod"
|
||||
|
||||
[Pod]
|
||||
PodName=test-pod
|
6
test/e2e/quadlet/pod.non-quadlet.container
Normal file
6
test/e2e/quadlet/pod.non-quadlet.container
Normal file
@ -0,0 +1,6 @@
|
||||
## assert-failed
|
||||
## assert-stderr-contains "pod test-pod is not Quadlet based"
|
||||
|
||||
[Container]
|
||||
Image=localhost/imagename
|
||||
Pod=test-pod
|
6
test/e2e/quadlet/pod.not-found.container
Normal file
6
test/e2e/quadlet/pod.not-found.container
Normal file
@ -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
|
13
test/e2e/quadlet/podmanargs.pod
Normal file
13
test/e2e/quadlet/podmanargs.pod
Normal file
@ -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
|
@ -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":
|
||||
@ -714,6 +776,8 @@ BOGUS=foo
|
||||
Entry("notify.container", "notify.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, ""),
|
||||
@ -821,6 +885,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, ""),
|
||||
)
|
||||
|
||||
})
|
||||
|
@ -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 <<EOF
|
||||
[Pod]
|
||||
PodName=$test_pod_name
|
||||
EOF
|
||||
|
||||
local quadlet_container_unit=pod_test_$(random_string).container
|
||||
local quadlet_container_file=$PODMAN_TMPDIR/$quadlet_container_unit
|
||||
cat > $quadlet_container_file <<EOF
|
||||
[Container]
|
||||
Image=$IMAGE
|
||||
Exec=sh -c "echo STARTED CONTAINER; echo "READY=1" | socat -u STDIN unix-sendto:\$NOTIFY_SOCKET; sleep inf"
|
||||
Pod=$quadlet_pod_unit
|
||||
EOF
|
||||
|
||||
# Use the same directory for all quadlet files to make sure later steps access previous ones
|
||||
mkdir $quadlet_tmpdir
|
||||
|
||||
# Have quadlet create the systemd unit file for the pod unit
|
||||
run_quadlet "$quadlet_pod_file" "$quadlet_tmpdir"
|
||||
# Save the pod service name since the variable will be overwritten
|
||||
local pod_service=$QUADLET_SERVICE_NAME
|
||||
|
||||
# Have quadlet create the systemd unit file for the container unit
|
||||
run_quadlet "$quadlet_container_file" "$quadlet_tmpdir"
|
||||
local container_service=$QUADLET_SERVICE_NAME
|
||||
local container_name=$QUADLET_CONTAINER_NAME
|
||||
|
||||
# Start the pod service
|
||||
service_setup $pod_service
|
||||
|
||||
# Pod should exist
|
||||
run_podman pod exists ${test_pod_name}
|
||||
|
||||
# Wait for systemd to activate the container service
|
||||
wait_for_command_output "systemctl show --property=ActiveState $container_service" "ActiveState=active"
|
||||
|
||||
# Container should exist
|
||||
run_podman container exists ${container_name}
|
||||
|
||||
# Shutdown the service
|
||||
service_cleanup $pod_service inactive
|
||||
|
||||
# The service of the container should be active
|
||||
run systemctl show --property=ActiveState "$container_service"
|
||||
assert "ActiveState=failed" \
|
||||
"quadlet - pod base: container service ActiveState"
|
||||
|
||||
# Container should not exist
|
||||
run_podman 1 container exists ${container_name}
|
||||
|
||||
run_podman rmi $(pause_image)
|
||||
}
|
||||
# vim: filetype=sh
|
||||
|
@ -1139,5 +1139,33 @@ function sleep_to_next_second() {
|
||||
sleep 0.$(printf '%04d' $((10000 - 10#$(date +%4N))))
|
||||
}
|
||||
|
||||
function wait_for_command_output() {
|
||||
local cmd="$1"
|
||||
local want="$2"
|
||||
local tries=20
|
||||
local sleep_delay=0.5
|
||||
|
||||
case "${#*}" in
|
||||
2) ;;
|
||||
4) tries="$3"
|
||||
sleep_delay="$4"
|
||||
;;
|
||||
*) die "Internal error: 'wait_for_command_output' requires two or four arguments" ;;
|
||||
esac
|
||||
|
||||
while [[ $tries -gt 0 ]]; do
|
||||
echo "$_LOG_PROMPT $cmd"
|
||||
run $cmd
|
||||
echo "$output"
|
||||
if [[ "$output" = "$want" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
sleep $sleep_delay
|
||||
tries=$((tries - 1))
|
||||
done
|
||||
die "Timed out waiting for '$cmd' to return '$want'"
|
||||
}
|
||||
|
||||
# END miscellaneous tools
|
||||
###############################################################################
|
||||
|
@ -59,6 +59,8 @@ quadlet_to_service_name() {
|
||||
suffix="-network"
|
||||
elif [ "$extension" == "image" ]; then
|
||||
suffix="-image"
|
||||
elif [ "$extension" == "pod" ]; then
|
||||
suffix="-pod"
|
||||
fi
|
||||
|
||||
echo "$filename$suffix.service"
|
||||
|
Reference in New Issue
Block a user