mirror of
https://github.com/containers/podman.git
synced 2025-10-18 03:33:32 +08:00
Merge pull request #20762 from ygalblum/quadlet-pod
Quadlet - Add support for .pod units
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 |
|
||||
@ -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 `<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
|
||||
@ -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)**,
|
||||
|
@ -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
|
||||
}
|
||||
|
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":
|
||||
@ -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, ""),
|
||||
)
|
||||
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -1168,5 +1168,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