mirror of
https://github.com/containers/podman.git
synced 2026-03-13 08:01:19 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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`:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
6
test/e2e/quadlet/all-tags.image
Normal file
6
test/e2e/quadlet/all-tags.image
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-final-args localhost/imagename
|
||||
## assert-podman-args --all-tags
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
||||
AllTags=yes
|
||||
8
test/e2e/quadlet/arch-os.image
Normal file
8
test/e2e/quadlet/arch-os.image
Normal 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
|
||||
6
test/e2e/quadlet/arch.image
Normal file
6
test/e2e/quadlet/arch.image
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-final-args localhost/imagename
|
||||
## assert-podman-args --arch=aarch64
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
||||
Arch=aarch64
|
||||
6
test/e2e/quadlet/auth.image
Normal file
6
test/e2e/quadlet/auth.image
Normal 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
|
||||
8
test/e2e/quadlet/basic.image
Normal file
8
test/e2e/quadlet/basic.image
Normal 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
|
||||
6
test/e2e/quadlet/certs.image
Normal file
6
test/e2e/quadlet/certs.image
Normal 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
|
||||
6
test/e2e/quadlet/creds.image
Normal file
6
test/e2e/quadlet/creds.image
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-final-args localhost/imagename
|
||||
## assert-podman-args --creds=myname:mypassword
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
||||
Creds=myname:mypassword
|
||||
6
test/e2e/quadlet/decrypt.image
Normal file
6
test/e2e/quadlet/decrypt.image
Normal 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
|
||||
5
test/e2e/quadlet/image-no-image.volume
Normal file
5
test/e2e/quadlet/image-no-image.volume
Normal file
@@ -0,0 +1,5 @@
|
||||
## assert-failed
|
||||
## assert-stderr-contains "the key Image is mandatory when using the image driver"
|
||||
|
||||
[Volume]
|
||||
Driver=image
|
||||
6
test/e2e/quadlet/image.volume
Normal file
6
test/e2e/quadlet/image.volume
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-args --driver=image
|
||||
## assert-podman-args --opt image=localhost/imagename
|
||||
|
||||
[Volume]
|
||||
Driver=image
|
||||
Image=localhost/imagename
|
||||
4
test/e2e/quadlet/no-image.image
Normal file
4
test/e2e/quadlet/no-image.image
Normal file
@@ -0,0 +1,4 @@
|
||||
## assert-failed
|
||||
## assert-stderr-contains "no Image key specified"
|
||||
|
||||
[Image]
|
||||
6
test/e2e/quadlet/os.image
Normal file
6
test/e2e/quadlet/os.image
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-final-args localhost/imagename
|
||||
## assert-podman-args --os=windows
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
||||
OS=windows
|
||||
6
test/e2e/quadlet/tls-verify.image
Normal file
6
test/e2e/quadlet/tls-verify.image
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-final-args localhost/imagename
|
||||
## assert-podman-args --tls-verify=false
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
||||
TLSVerify=no
|
||||
6
test/e2e/quadlet/variant.image
Normal file
6
test/e2e/quadlet/variant.image
Normal file
@@ -0,0 +1,6 @@
|
||||
## assert-podman-final-args localhost/imagename
|
||||
## assert-podman-args --variant=arm/v5
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
||||
Variant=arm/v5
|
||||
@@ -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, ""),
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user