Quadlet - add support for .image units

main
----
Use order number to order the units processing
Process .image file
Usage of .image file should not log Ambiguous Name warning
Use AmbiguousName for .volume and .image units

Quadlet
-------
Convert .image files
Add driver and Image keys to .volume files
Handle usage of .image as Image

Man Page
--------
Add comments for new keys in .volume file
Add comment about using .image files as images
Add section about .image units

Tests
-----
Add integration tests
Add system test

Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
Co-authored-by: Tom Sweeney <tsweeney@redhat.com>
This commit is contained in:
Ygal Blum
2023-09-07 18:50:25 +03:00
parent d912e735a3
commit 55ca571e55
20 changed files with 603 additions and 75 deletions

View File

@@ -45,12 +45,15 @@ var (
)
var (
void struct{}
supportedExtensions = map[string]struct{}{
".container": void,
".volume": void,
".kube": void,
".network": void,
void struct{}
// Key: Extension
// Value: Processing order for resource naming dependecies
supportedExtensions = map[string]int{
".container": 3,
".volume": 2,
".kube": 3,
".network": 2,
".image": 1,
}
)
@@ -364,13 +367,16 @@ func isUnambiguousName(imageName string) bool {
//
// We implement a simple version of this from scratch here to avoid
// a huge dependency in the generator just for a warning.
func warnIfAmbiguousName(container *parser.UnitFile) {
imageName, ok := container.Lookup(quadlet.ContainerGroup, quadlet.KeyImage)
func warnIfAmbiguousName(unit *parser.UnitFile, group string) {
imageName, ok := unit.Lookup(group, quadlet.KeyImage)
if !ok {
return
}
if strings.HasSuffix(imageName, ".image") {
return
}
if !isUnambiguousName(imageName) {
Logf("Warning: %s specifies the image \"%s\" which not a fully qualified image name. This is not ideal for performance and security reasons. See the podman-pull manpage discussion of short-name-aliases.conf for details.", container.Filename, imageName)
Logf("Warning: %s specifies the image \"%s\" which not a fully qualified image name. This is not ideal for performance and security reasons. See the podman-pull manpage discussion of short-name-aliases.conf for details.", unit.Filename, imageName)
}
}
@@ -452,8 +458,15 @@ func process() error {
// Sort unit files according to potential inter-dependencies, with Volume and Network units
// taking precedence over all others.
sort.Slice(units, func(i, j int) bool {
name := units[i].Filename
return strings.HasSuffix(name, ".volume") || strings.HasSuffix(name, ".network")
getOrder := func(i int) int {
ext := filepath.Ext(units[i].Filename)
order, ok := supportedExtensions[ext]
if !ok {
return 0
}
return order
}
return getOrder(i) < getOrder(j)
})
// A map of network/volume unit file-names, against their calculated names, as needed by Podman.
@@ -466,14 +479,18 @@ func process() error {
switch {
case strings.HasSuffix(unit.Filename, ".container"):
warnIfAmbiguousName(unit)
warnIfAmbiguousName(unit, quadlet.ContainerGroup)
service, err = quadlet.ConvertContainer(unit, resourceNames, isUserFlag)
case strings.HasSuffix(unit.Filename, ".volume"):
service, name, err = quadlet.ConvertVolume(unit, unit.Filename)
warnIfAmbiguousName(unit, quadlet.VolumeGroup)
service, name, err = quadlet.ConvertVolume(unit, unit.Filename, resourceNames)
case strings.HasSuffix(unit.Filename, ".kube"):
service, err = quadlet.ConvertKube(unit, resourceNames, isUserFlag)
case strings.HasSuffix(unit.Filename, ".network"):
service, name, err = quadlet.ConvertNetwork(unit, unit.Filename)
case strings.HasSuffix(unit.Filename, ".image"):
warnIfAmbiguousName(unit, quadlet.ImageGroup)
service, name, err = quadlet.ConvertImage(unit)
default:
Logf("Unsupported file type %q", unit.Filename)
continue

View File

@@ -360,8 +360,13 @@ The image to run in the container.
It is recommended to use a fully qualified image name rather than a short name, both for
performance and robustness reasons.
The format of the name is the same as when passed to `podman run`, so it supports e.g., using
`:tag` or using digests guarantee a specific image version.
The format of the name is the same as when passed to `podman pull`. So, it supports using
`:tag` or digests to guarantee the specific image version.
As a special case, if the `name` of the image ends with `.image`, Quadlet will use the image
pulled by the corresponding `.image` file, and the generated systemd service contains
a dependency on the `$name-image.service`.
Note that the corresponding `.image` file must exist.
### `IP=`
@@ -626,7 +631,7 @@ This key may be used multiple times
### `ExitCodePropagation=`
Control how the main PID of the systemd service should exit. The following values are supported:
Control how the main PID of the systemd service should exit. The following values are supported:
- `all`: exit non-zero if all containers have failed (i.e., exited non-zero)
- `any`: exit non-zero if any container has failed
- `none`: exit zero and ignore failed containers
@@ -640,7 +645,7 @@ Equivalent to the Podman `--log-driver` option.
### `Mask=`
Specify the paths to mask separated by a colon. `Mask=/path/1:/path/2`. A masked path cannot be accessed inside the container.
Specify the paths to mask separated by a colon. `Mask=/path/1:/path/2`. A masked path cannot be accessed inside the container.
### `Network=`
@@ -792,7 +797,7 @@ This is equivalent to the Podman `--ipam-driver` option
### `IPRange=`
Allocate container IP from a range. The range must be a either a complete subnet in CIDR notation or be
Allocate container IP from a range. The range must be a either a complete subnet in CIDR notation or be
in the `<startIP>-<endIP>` syntax which allows for a more flexible range compared to the CIDR subnet.
The ip-range option must be used with a subnet option.
@@ -863,15 +868,17 @@ as Podman otherwise creates volumes with the default options.
Valid options for `[Volume]` are listed below:
| **[Volume] options** | **podman volume create equivalent** |
|---------------------------|-------------------------------------|
| Device=tmpfs | --opt device=tmpfs |
| Copy=true | --opt copy |
| Group=192 | --opt group=192 |
| Label="foo=bar" | --label "foo=bar" |
| Options=XYZ | --opt XYZ |
| PodmanArgs=--driver=image | --driver=image |
| VolumeName=foo | podman volume create foo |
| **[Volume] options** | **podman volume create equivalent** |
|-------------------------------------|-------------------------------------------|
| Device=tmpfs | --opt device=tmpfs |
| Driver=image | --driver=image |
| Copy=true | --opt copy |
| Group=192 | --opt group=192 |
| Image=quay.io/centos/centos\:latest | --opt image=quay.io/centos/centos\:latest |
| Label="foo=bar" | --label "foo=bar" |
| Options=XYZ | --opt XYZ |
| PodmanArgs=--driver=image | --driver=image |
| VolumeName=foo | podman volume create foo |
Supported keys in `[Volume]` section are:
@@ -884,10 +891,30 @@ volume on the first run.
The path of a device which is mounted for the volume.
### `Driver=`
Specify the volume driver name. When set to `image`, the `Image` key must also be set.
This is equivalent to the Podman `--driver` option.
### `Group=`
The host (numeric) GID, or group name to use as the group for the volume
### `Image=`
Specifies the image the volume is based on when `Driver` is set to the `image`.
It is recommended to use a fully qualified image name rather than a short name, both for
performance and robustness reasons.
The format of the name is the same as when passed to `podman pull`. So, it supports using
`:tag` or digests to guarantee the specific image version.
As a special case, if the `name` of the image ends with `.image`, Quadlet will use the image
pulled by the corresponding `.image` file, and the generated systemd service contains
a dependency on the `$name-image.service`.
Note that the corresponding `.image` file must exist.
### `Label=`
Set one or more OCI labels on the volume. The format is a list of
@@ -926,6 +953,107 @@ The (optional) name of the Podman volume. If this is not specified, the default
`systemd-%N` is used, which is the same as the unit name but with a `systemd-` prefix to avoid
conflicts with user-managed volumes.
## Image units [Image]
Image files are named with a `.image` extension and contain a section `[Image]` describing the
container image pull command. The generated service is a one-time command that ensures that the image
exists on the host, pulling it if needed.
Using image units allows containers and volumes to depend on images being automatically pulled. This is
particularly interesting when using special options to control image pulls.
Valid options for `[Image]` are listed below:
| **[Image] options** | **podman image pull equivalent** |
|-------------------------------------|-------------------------------------------------|
| AllTags=true | --all-tags |
| Arch=aarch64 | --arch=aarch64 |
| AuthFile=/etc/registry/auth\.json | --authfile=/etc/registry/auth\.json |
| CertDir=/etc/registery/certs | --cert-dir=/etc/registery/certs |
| Creds=myname\:mypassword | --creds=myname\:mypassword |
| DecryptionKey=/etc/registery\.key | --decryption-key=/etc/registery\.key |
| Image=quay.io/centos/centos\:latest | podman image pull quay.io/centos/centos\:latest |
| OS=windows | --os=windows |
| PodmanArgs=--os=linux | --os=linux |
| TLSVerify=false | --tls-verify=false |
| Variant=arm/v7 | --variant=arm/v7 |
### `AllTags=`
All tagged images in the repository are pulled.
This is equivalent to the Podman `--all-tags` option.
### `Arch=`
Override the architecture, defaults to hosts, of the image to be pulled.
This is equivalent to the Podman `--arch` option.
### `AuthFile=`
Path of the authentication file.
This is equivalent to the Podman `--authfile` option.
### `CertDir=`
Use certificates at path (*.crt, *.cert, *.key) to connect to the registry.
This is equivalent to the Podman `--cert-dir` option.
### `Creds=`
The `[username[:password]]` to use to authenticate with the registry, if required.
This is equivalent to the Podman `--creds` option.
### `DecryptionKey=`
The `[key[:passphrase]]` to be used for decryption of images.
This is equivalent to the Podman `--decryption-key` option.
### `Image=`
The image to pull.
It is recommended to use a fully qualified image name rather than a short name, both for
performance and robustness reasons.
The format of the name is the same as when passed to `podman pull`. So, it supports using
`:tag` or digests to guarantee the specific image version.
### `OS=`
Override the OS, defaults to hosts, of the image to be pulled.
This is equivalent to the Podman `--os` option.
### `PodmanArgs=`
This key contains a list of arguments passed directly to the end of the `podman image pull` command
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.
### `TLSVerify=`
Require HTTPS and verification of certificates when contacting registries.
This is equivalent to the Podman `--tls-verify` option.
### `Variant=`
Override the default architecture variant of the container image.
This is equivalent to the Podman `--variant` option.
## EXAMPLES
Example `test.container`:

View File

@@ -29,10 +29,12 @@ const (
ServiceGroup = "Service"
UnitGroup = "Unit"
VolumeGroup = "Volume"
ImageGroup = "Image"
XContainerGroup = "X-Container"
XKubeGroup = "X-Kube"
XNetworkGroup = "X-Network"
XVolumeGroup = "X-Volume"
XImageGroup = "X-Image"
)
// Systemd Unit file keys
@@ -44,8 +46,14 @@ const (
const (
KeyAddCapability = "AddCapability"
KeyAddDevice = "AddDevice"
KeyAllTags = "AllTags"
KeyAnnotation = "Annotation"
KeyArch = "Arch"
KeyAuthFile = "AuthFile"
KeyAutoUpdate = "AutoUpdate"
KeyCertDir = "CertDir"
KeyCreds = "Creds"
KeyDecryptionKey = "DecryptionKey"
KeyConfigMap = "ConfigMap"
KeyContainerName = "ContainerName"
KeyCopy = "Copy"
@@ -53,6 +61,7 @@ const (
KeyDNS = "DNS"
KeyDNSOption = "DNSOption"
KeyDNSSearch = "DNSSearch"
KeyDriver = "Driver"
KeyDropCapability = "DropCapability"
KeyEnvironment = "Environment"
KeyEnvironmentFile = "EnvironmentFile"
@@ -94,6 +103,7 @@ const (
KeyNoNewPrivileges = "NoNewPrivileges"
KeyNotify = "Notify"
KeyOptions = "Options"
KeyOS = "OS"
KeyPidsLimit = "PidsLimit"
KeyPodmanArgs = "PodmanArgs"
KeyPublishPort = "PublishPort"
@@ -116,12 +126,14 @@ const (
KeyShmSize = "ShmSize"
KeySysctl = "Sysctl"
KeyTimezone = "Timezone"
KeyTLSVerify = "TLSVerify"
KeyTmpfs = "Tmpfs"
KeyType = "Type"
KeyUlimit = "Ulimit"
KeyUnmask = "Unmask"
KeyUser = "User"
KeyUserNS = "UserNS"
KeyVariant = "Variant"
KeyVolatileTmp = "VolatileTmp"
KeyVolume = "Volume"
KeyVolumeName = "VolumeName"
@@ -206,7 +218,9 @@ var (
supportedVolumeKeys = map[string]bool{
KeyCopy: true,
KeyDevice: true,
KeyDriver: true,
KeyGroup: true,
KeyImage: true,
KeyLabel: true,
KeyOptions: true,
KeyPodmanArgs: true,
@@ -249,6 +263,21 @@ var (
KeyUserNS: true,
KeyYaml: true,
}
// Supported keys in "Image" group
supportedImageKeys = map[string]bool{
KeyAllTags: true,
KeyArch: true,
KeyAuthFile: true,
KeyCertDir: true,
KeyCreds: true,
KeyDecryptionKey: true,
KeyImage: true,
KeyOS: true,
KeyPodmanArgs: true,
KeyTLSVerify: true,
KeyVariant: true,
}
)
func replaceExtension(name string, extension string, extraPrefix string, extraSuffix string) string {
@@ -349,6 +378,13 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse
return nil, fmt.Errorf("the Image And Rootfs keys conflict can not be specified together")
}
if len(image) > 0 {
var err error
if image, err = handleImageSource(image, service, names); err != nil {
return nil, err
}
}
containerName, ok := container.Lookup(ContainerGroup, KeyContainerName)
if !ok || len(containerName) == 0 {
// By default, We want to name the container by the service name
@@ -860,7 +896,7 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, st
// The original Volume group is kept around as X-Volume.
// Also returns the canonical volume name, either auto-generated or user-defined via the VolumeName
// key-value.
func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, string, error) {
func ConvertVolume(volume *parser.UnitFile, name string, names map[string]string) (*parser.UnitFile, string, error) {
service := volume.Dup()
service.Filename = replaceExtension(volume.Filename, ".service", "", "-volume")
@@ -884,60 +920,81 @@ func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, stri
podman := NewPodmanCmdline("volume", "create", "--ignore")
var opts strings.Builder
opts.WriteString("o=")
if volume.HasKey(VolumeGroup, "User") {
uid := volume.LookupUint32(VolumeGroup, "User", 0)
if opts.Len() > 2 {
opts.WriteString(",")
}
opts.WriteString(fmt.Sprintf("uid=%d", uid))
}
if volume.HasKey(VolumeGroup, "Group") {
gid := volume.LookupUint32(VolumeGroup, "Group", 0)
if opts.Len() > 2 {
opts.WriteString(",")
}
opts.WriteString(fmt.Sprintf("gid=%d", gid))
}
copy, ok := volume.LookupBoolean(VolumeGroup, KeyCopy)
driver, ok := volume.Lookup(VolumeGroup, KeyDriver)
if ok {
if copy {
podman.add("--opt", "copy")
} else {
podman.add("--opt", "nocopy")
podman.addf("--driver=%s", driver)
}
var opts strings.Builder
if driver == "image" {
opts.WriteString("image=")
imageName, ok := volume.Lookup(VolumeGroup, KeyImage)
if !ok {
return nil, "", fmt.Errorf("the key %s is mandatory when using the image driver", KeyImage)
}
}
devValid := false
dev, ok := volume.Lookup(VolumeGroup, KeyDevice)
if ok && len(dev) != 0 {
podman.add("--opt", fmt.Sprintf("device=%s", dev))
devValid = true
}
devType, ok := volume.Lookup(VolumeGroup, KeyType)
if ok && len(devType) != 0 {
if devValid {
podman.add("--opt", fmt.Sprintf("type=%s", devType))
} else {
return nil, "", fmt.Errorf("key Type can't be used without Device")
imageName, err := handleImageSource(imageName, service, names)
if err != nil {
return nil, "", err
}
}
mountOpts, ok := volume.Lookup(VolumeGroup, KeyOptions)
if ok && len(mountOpts) != 0 {
if devValid {
opts.WriteString(imageName)
} else {
opts.WriteString("o=")
if volume.HasKey(VolumeGroup, "User") {
uid := volume.LookupUint32(VolumeGroup, "User", 0)
if opts.Len() > 2 {
opts.WriteString(",")
}
opts.WriteString(mountOpts)
} else {
return nil, "", fmt.Errorf("key Options can't be used without Device")
opts.WriteString(fmt.Sprintf("uid=%d", uid))
}
if volume.HasKey(VolumeGroup, "Group") {
gid := volume.LookupUint32(VolumeGroup, "Group", 0)
if opts.Len() > 2 {
opts.WriteString(",")
}
opts.WriteString(fmt.Sprintf("gid=%d", gid))
}
copy, ok := volume.LookupBoolean(VolumeGroup, KeyCopy)
if ok {
if copy {
podman.add("--opt", "copy")
} else {
podman.add("--opt", "nocopy")
}
}
devValid := false
dev, ok := volume.Lookup(VolumeGroup, KeyDevice)
if ok && len(dev) != 0 {
podman.add("--opt", fmt.Sprintf("device=%s", dev))
devValid = true
}
devType, ok := volume.Lookup(VolumeGroup, KeyType)
if ok && len(devType) != 0 {
if devValid {
podman.add("--opt", fmt.Sprintf("type=%s", devType))
} else {
return nil, "", fmt.Errorf("key Type can't be used without Device")
}
}
mountOpts, ok := volume.Lookup(VolumeGroup, KeyOptions)
if ok && len(mountOpts) != 0 {
if devValid {
if opts.Len() > 2 {
opts.WriteString(",")
}
opts.WriteString(mountOpts)
} else {
return nil, "", fmt.Errorf("key Options can't be used without Device")
}
}
}
@@ -1082,6 +1139,70 @@ func ConvertKube(kube *parser.UnitFile, names map[string]string, isUser bool) (*
return service, nil
}
func ConvertImage(image *parser.UnitFile) (*parser.UnitFile, string, error) {
service := image.Dup()
service.Filename = replaceExtension(image.Filename, ".service", "", "-image")
if image.Path != "" {
service.Add(UnitGroup, "SourcePath", image.Path)
}
if err := checkForUnknownKeys(image, ImageGroup, supportedImageKeys); err != nil {
return nil, "", err
}
imageName, ok := image.Lookup(ImageGroup, KeyImage)
if !ok || len(imageName) == 0 {
return nil, "", fmt.Errorf("no Image key specified")
}
/* Rename old Network group to x-Network so that systemd ignores it */
service.RenameGroup(ImageGroup, XImageGroup)
// Need the containers filesystem mounted to start podman
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
podman := NewPodmanCmdline("image", "pull")
stringKeys := map[string]string{
KeyArch: "--arch",
KeyAuthFile: "--authfile",
KeyCertDir: "--cert-dir",
KeyCreds: "--creds",
KeyDecryptionKey: "--decryption-key",
KeyOS: "--os",
KeyVariant: "--variant",
}
boolKeys := map[string]string{
KeyAllTags: "--all-tags",
KeyTLSVerify: "--tls-verify",
}
for key, flag := range stringKeys {
lookupAndAddString(image, ImageGroup, key, flag, podman)
}
for key, flag := range boolKeys {
lookupAndAddBoolean(image, ImageGroup, key, flag, podman)
}
handlePodmanArgs(image, ImageGroup, podman)
podman.add(imageName)
service.AddCmdline(ServiceGroup, "ExecStart", podman.Args)
service.Setv(ServiceGroup,
"Type", "oneshot",
"RemainAfterExit", "yes",
// The default syslog identifier is the exec basename (podman) which isn't very useful here
"SyslogIdentifier", "%N")
return service, imageName, nil
}
func handleUserRemap(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline, isUser, supportManual bool) error {
// ignore Remap keys if UserNS is set
if userns, ok := unitFile.Lookup(groupName, KeyUserNS); ok && len(userns) > 0 {
@@ -1383,3 +1504,37 @@ func handleSetWorkingDirectory(kube, serviceUnitFile *parser.UnitFile) error {
return nil
}
func lookupAndAddString(unit *parser.UnitFile, group, key, flag string, podman *PodmanCmdline) {
val, ok := unit.Lookup(group, key)
if ok && len(val) > 0 {
podman.addf("%s=%s", flag, val)
}
}
func lookupAndAddBoolean(unit *parser.UnitFile, group, key, flag string, podman *PodmanCmdline) {
val, ok := unit.LookupBoolean(group, key)
if ok {
podman.addBool(flag, val)
}
}
func handleImageSource(quadletImageName string, serviceUnitFile *parser.UnitFile, names map[string]string) (string, error) {
if strings.HasSuffix(quadletImageName, ".image") {
// since there is no default name conversion, the actual image name must exist in the names map
imageName, ok := names[quadletImageName]
if !ok {
return "", fmt.Errorf("requested Quadlet image %s was not found", imageName)
}
// the systemd unit name is $name-image.service
imageServiceName := replaceExtension(quadletImageName, ".service", "", "-image")
serviceUnitFile.Add(UnitGroup, "Requires", imageServiceName)
serviceUnitFile.Add(UnitGroup, "After", imageServiceName)
quadletImageName = imageName
}
return quadletImageName, nil
}

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --all-tags
[Image]
Image=localhost/imagename
AllTags=yes

View File

@@ -0,0 +1,8 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --os=windows
## assert-podman-args --arch=arm64
[Image]
Image=localhost/imagename
OS=windows
Arch=arm64

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --arch=aarch64
[Image]
Image=localhost/imagename
Arch=aarch64

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --authfile=/etc/certs/auth.json
[Image]
Image=localhost/imagename
AuthFile=/etc/certs/auth.json

View File

@@ -0,0 +1,8 @@
## assert-podman-final-args localhost/imagename
## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
## assert-key-is "Service" "Type" "oneshot"
## assert-key-is "Service" "RemainAfterExit" "yes"
## assert-key-is "Service" "SyslogIdentifier" "%N"
[Image]
Image=localhost/imagename

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --cert-dir=/etc/certs/auth.json
[Image]
Image=localhost/imagename
CertDir=/etc/certs/auth.json

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --creds=myname:mypassword
[Image]
Image=localhost/imagename
Creds=myname:mypassword

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --decryption-key=/etc/keys/decrypt:passphrase
[Image]
Image=localhost/imagename
DecryptionKey=/etc/keys/decrypt:passphrase

View File

@@ -0,0 +1,5 @@
## assert-failed
## assert-stderr-contains "the key Image is mandatory when using the image driver"
[Volume]
Driver=image

View File

@@ -0,0 +1,6 @@
## assert-podman-args --driver=image
## assert-podman-args --opt image=localhost/imagename
[Volume]
Driver=image
Image=localhost/imagename

View File

@@ -0,0 +1,4 @@
## assert-failed
## assert-stderr-contains "no Image key specified"
[Image]

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --os=windows
[Image]
Image=localhost/imagename
OS=windows

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --tls-verify=false
[Image]
Image=localhost/imagename
TLSVerify=no

View File

@@ -0,0 +1,6 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --variant=arm/v5
[Image]
Image=localhost/imagename
Variant=arm/v5

View File

@@ -35,6 +35,8 @@ func loadQuadletTestcase(path string) *quadletTestcase {
service += "-volume"
case ".network":
service += "-network"
case ".image":
service += "-image"
}
service += ".service"
@@ -635,6 +637,8 @@ BOGUS=foo
Entry("name.volume", "name.volume", 0, ""),
Entry("podmanargs.volume", "podmanargs.volume", 0, ""),
Entry("uid.volume", "uid.volume", 0, ""),
Entry("image.volume", "image.volume", 0, ""),
Entry("image-no-image.volume", "image-no-image.volume", 1, "converting \"image-no-image.volume\": the key Image is mandatory when using the image driver"),
Entry("Absolute Path", "absolute.path.kube", 0, ""),
Entry("Basic kube", "basic.kube", 0, ""),
@@ -676,6 +680,19 @@ BOGUS=foo
Entry("Network - Subnets", "subnets.network", 0, ""),
Entry("Network - multiple subnet, gateway and range", "subnet-trio.multiple.network", 0, ""),
Entry("Network - subnet, gateway and range", "subnet-trio.network", 0, ""),
Entry("Image - Basic", "basic.image", 0, ""),
Entry("Image - No Image", "no-image.image", 1, "converting \"no-image.image\": no Image key specified"),
Entry("Image - Architecture", "arch.image", 0, ""),
Entry("Image - Auth File", "auth.image", 0, ""),
Entry("Image - Certificates", "certs.image", 0, ""),
Entry("Image - Credentials", "creds.image", 0, ""),
Entry("Image - Decryption Key", "decrypt.image", 0, ""),
Entry("Image - OS Key", "os.image", 0, ""),
Entry("Image - Variant Key", "variant.image", 0, ""),
Entry("Image - All Tags", "all-tags.image", 0, ""),
Entry("Image - TLS Verify", "tls-verify.image", 0, ""),
Entry("Image - Arch and OS", "arch-os.image", 0, ""),
)
})

View File

@@ -4,6 +4,8 @@
#
load helpers
load helpers.network
load helpers.registry
load helpers.systemd
UNIT_FILES=()
@@ -1013,4 +1015,126 @@ EOF
run_podman rmi $(pause_image)
}
@test "quadlet - image files" {
registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}
image_on_local_registry=$registry/quadlet_image_test:$(random_string)
authfile=$PODMAN_TMPDIR/authfile.json
# First, start the registry and populate the authfile that we can use for the test.
start_registry
run_podman login --authfile=$authfile \
--tls-verify=false \
--username ${PODMAN_LOGIN_USER} \
--password ${PODMAN_LOGIN_PASS} \
$registry
run_podman image tag $IMAGE $image_on_local_registry
run_podman image push --tls-verify=false --authfile=$authfile $image_on_local_registry
local image_for_test=$image_on_local_registry
# Remove the local image to make sure it will be pulled again
run_podman image rm --ignore $image_for_test
local quadlet_image_unit=image_test_$(random_string).image
local quadlet_image_file=$PODMAN_TMPDIR/$quadlet_image_unit
cat > $quadlet_image_file <<EOF
[Image]
Image=$image_for_test
AuthFile=$authfile
TLSVerify=false
EOF
# Use the same directory for all quadlet files to make sure later steps access previous ones
local quadlet_tmpdir=$PODMAN_TMPDIR/quadlets
mkdir $quadlet_tmpdir
# Have quadlet create the systemd unit file for the image unit
run_quadlet "$quadlet_image_file" "$quadlet_tmpdir"
# Save the image service name since the variable will be overwritten
local image_service=$QUADLET_SERVICE_NAME
local quadlet_volume_unit=image_test_$(random_string).volume
local quadlet_volume_file=$PODMAN_TMPDIR/$quadlet_volume_unit
cat > $quadlet_volume_file <<EOF
[Volume]
Driver=image
Image=$quadlet_image_unit
EOF
# Have quadlet create the systemd unit file for the image unit
run_quadlet "$quadlet_volume_file" "$quadlet_tmpdir"
# Save the image service name since the variable will be overwritten
local volume_service=$QUADLET_SERVICE_NAME
local volume_name=systemd-$(basename $quadlet_volume_file .volume)
local quadlet_container_unit=image_test_$(random_string).container
local quadlet_container_file=$PODMAN_TMPDIR/$quadlet_container_unit
cat > $quadlet_container_file <<EOF
[Container]
Image=$quadlet_image_unit
Volume=$quadlet_volume_unit:/vol
Exec=sh -c "echo STARTED CONTAINER; echo "READY=1" | socat -u STDIN unix-sendto:\$NOTIFY_SOCKET; sleep inf"
EOF
# Image should not exist
run_podman 1 image exists ${image_for_test}
# Volume should not exist
run_podman 1 volume exists ${volume_name}
# Have quadlet create the systemd unit file for the image unit
run_quadlet "$quadlet_container_file" "$quadlet_tmpdir"
local container_service=$QUADLET_SERVICE_NAME
local container_name=$QUADLET_CONTAINER_NAME
service_setup $container_service
# Image system unit should be active
run systemctl show --property=ActiveState "$image_service"
assert "$output" = "ActiveState=active" \
"quadlet - image files: image should be active via dependency but is not"
# Volume system unit should be active
run systemctl show --property=ActiveState "$volume_service"
assert "$output" = "ActiveState=active" \
"quadlet - image files: volume should be active via dependency but is not"
# Image should exist
run_podman image exists ${image_for_test}
# Volume should exist
run_podman volume exists ${volume_name}
# Verify that the volume was created correctly
run_podman volume inspect --format "{{ .Driver }}" $volume_name
assert "$output" = "image" \
"quadlet - image files: volume driver should be image"
run_podman volume inspect --format "{{ .Options.image }}" $volume_name
assert "$output" = "$image_for_test" \
"quadlet - image files: the image for the volume should be $image_for_test"
# Verify that the container mounts the volume
run_podman container inspect --format "{{(index .Mounts 0).Type}}" $container_name
assert "$output" = "volume" \
"quadlet - image files: container should be attached to a volume of type volume"
run_podman container inspect --format "{{(index .Mounts 0).Name}}" $container_name
assert "$output" = "$volume_name" \
"quadlet - image files: container should be attached to the volume named $volume_name"
run_podman exec $QUADLET_CONTAINER_NAME cat /home/podman/testimage-id
assert "$output" = $PODMAN_TEST_IMAGE_TAG \
"quadlet - image files: incorrect testimage-id '$output' in root"
run_podman exec $QUADLET_CONTAINER_NAME cat /vol/home/podman/testimage-id
assert "$output" = $PODMAN_TEST_IMAGE_TAG \
"quadlet - image files: incorrect testimage-id '$output' in bound volume"
# Shutdown the service and remove the volume
service_cleanup $container_service failed
run_podman volume rm $volume_name
run_podman image rm --ignore $image_for_test
}
# vim: filetype=sh

View File

@@ -57,6 +57,8 @@ quadlet_to_service_name() {
suffix="-volume"
elif [ "$extension" == "network" ]; then
suffix="-network"
elif [ "$extension" == "image" ]; then
suffix="-image"
fi
echo "$filename$suffix.service"