Skip to content

Add artifact quadlet unit type support #26624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/quadlet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ func generateUnitsInfoMap(units []*parser.UnitFile) map[string]*quadlet.UnitInfo
// types), but still breaks the dependency cycle between .volume and .build ([Volume] can
// have Image=some.build, and [Build] can have Volume=some.volume:/some-volume)
resourceName = quadlet.GetBuiltImageName(unit)
case strings.HasSuffix(unit.Filename, ".artifact"):
serviceName = quadlet.GetArtifactServiceName(unit)
case strings.HasSuffix(unit.Filename, ".pod"):
serviceName = quadlet.GetPodServiceName(unit)
containers = make([]string, 0)
Expand Down Expand Up @@ -754,6 +756,9 @@ func process() bool {
service, err = quadlet.ConvertImage(unit, unitsInfoMap, isUserFlag)
case strings.HasSuffix(unit.Filename, ".build"):
service, warnings, err = quadlet.ConvertBuild(unit, unitsInfoMap, isUserFlag)
case strings.HasSuffix(unit.Filename, ".artifact"):
warnIfAmbiguousName(unit, quadlet.ArtifactGroup)
service, err = quadlet.ConvertArtifact(unit, unitsInfoMap, isUserFlag)
case strings.HasSuffix(unit.Filename, ".pod"):
service, warnings, err = quadlet.ConvertPod(unit, unit.Filename, unitsInfoMap, isUserFlag)
default:
Expand Down
141 changes: 138 additions & 3 deletions docs/source/markdown/podman-systemd.unit.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ podman\-systemd.unit - systemd units using Podman Quadlet

## SYNOPSIS

*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image, *name*.build *name*.pod
*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image, *name*.build *name*.pod, *name*.artifact

### Podman rootful unit search path

Expand Down Expand Up @@ -48,7 +48,7 @@ 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`, `.network`, `.build`, `.pod` and `.kube`, and for each file generates a similarly named `.service` file. Be aware that
`.volume`, `.network`, `.build`, `.pod`, `.kube`, and `.artifact`, 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.
Expand Down Expand Up @@ -104,7 +104,7 @@ Quadlet requires the use of cgroup v2, use `podman info --format {{.Host.Cgroups

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,
`forking` for `.pod` files, and `oneshot` for `.volume`, `.network`, `.build`, and `.image` files.
`forking` for `.pod` files, and `oneshot` for `.volume`, `.network`, `.build`, `.image`, and `.artifact` files.

However, `Type` may be explicitly set to `oneshot` for `.container` and `.kube` files when no containers are expected
to run once `podman` exits.
Expand Down Expand Up @@ -2075,6 +2075,133 @@ Override the default architecture variant of the container image.

This is equivalent to the Podman `--variant` option.

## Artifact units [Artifact]

### WARNING: Experimental Unit

This unit is considered experimental and still in development. Inputs, options, and outputs are all subject to change.
Comment on lines +2080 to +2082
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that? Is it because podman artifact is at this state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I just added the same text from the artifact section in podman


Artifact units are named with a `.artifact` extension and contain a `[Artifact]` section describing
the container artifact pull command. The generated service is a one-time command that ensures that the artifact
exists on the host, pulling it if needed.

Using artifact units allows containers to depend on artifacts being automatically pulled. This is
particularly useful for managing artifacts that containers need to mount or access.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should mention that the Artifact key is required

Valid options for `[Artifact]` are listed below:

| **[Artifact] options** | **podman artifact equivalent** |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be podman artifact pull equivalent?

|---------------------------------------------|--------------------------------------------------------|
| Arch=aarch64 | --arch=aarch64 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code for podma artifact pull, I don't see the support for the following flags

  • arch
  • os
  • variant
  • policy

| Artifact=quay\.io/foobar/artifact:special | podman artifact pull quay\.io/foobar/artifact:special |
| AuthFile=/etc/registry/auth\.json | --authfile=/etc/registry/auth\.json |
| CertDir=/etc/registry/certs | --cert-dir=/etc/registry/certs |
| DecryptionKey=/etc/registry\.key | --decryption-key=/etc/registry\.key |
| GlobalArgs=--log-level=debug | --log-level=debug |
| OS=windows | --os=windows |
| PodmanArgs=--os=linux | --os=linux |
| Policy=always | --policy=always |
| Quiet=true | --quiet |
| Retry=5 | --retry=5 |
| RetryDelay=10s | --retry-delay=10s |
| TLSVerify=false | --tls-verify=false |
| Variant=arm/v7 | --variant=arm/v7 |

### `Arch=`

Override the architecture, defaults to hosts, of the artifact to be pulled.

This is equivalent to the Podman `--arch` option.

### `Artifact=`

The artifact to pull from a registry onto the local machine. This is the only required key for artifact units.

It is recommended to use a fully qualified artifact name rather than a short name, both for
performance and robustness reasons.

### `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.

### `DecryptionKey=`

The `[key[:passphrase]]` to be used for decryption of artifacts.

This is equivalent to the Podman `--decryption-key` option.

### `GlobalArgs=`

This key contains a list of arguments passed directly between `podman` and `artifact`
in the generated file. 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.

### `OS=`

Override the OS, defaults to hosts, of the artifact 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 artifact` command
in the generated file (right before the artifact 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.

### `Policy=`

The pull policy to use when pulling the artifact.

This is equivalent to the Podman `--policy` option.

### `Quiet=`

Suppress output information when pulling artifacts.

This is equivalent to the Podman `--quiet` option.

### `Retry=`

Number of times to retry the artifact pull when a HTTP error occurs. Equivalent to the Podman `--retry` option.

### `RetryDelay=`

Delay between retries. Equivalent to the Podman `--retry-delay` option.

### `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 artifact.

This is equivalent to the Podman `--variant` option.


## Quadlet section [Quadlet]
Some quadlet specific configuration is shared between different unit types. Those settings
can be configured in the `[Quadlet]` section.
Expand Down Expand Up @@ -2179,6 +2306,14 @@ IPRange=172.16.0.0/28
Label=org.test.Key=value
```

Example `test.artifact`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a usage example. Where would test.artifact be used?

```
[Artifact]
Artifact=quay.io/example/my-artifact:latest
Arch=amd64
AuthFile=/etc/registry/auth.json
```

Example for Container in a Pod:

`test.pod`
Expand Down
89 changes: 85 additions & 4 deletions pkg/systemd/quadlet/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
UnitDirDistro = "/usr/share/containers/systemd"

// Names of commonly used systemd/quadlet group names
ArtifactGroup = "Artifact"
ContainerGroup = "Container"
InstallGroup = "Install"
KubeGroup = "Kube"
Expand All @@ -38,6 +39,7 @@ const (
ImageGroup = "Image"
BuildGroup = "Build"
QuadletGroup = "Quadlet"
XArtifactGroup = "X-Artifact"
XContainerGroup = "X-Container"
XKubeGroup = "X-Kube"
XNetworkGroup = "X-Network"
Expand All @@ -61,6 +63,7 @@ const (
KeyAllTags = "AllTags"
KeyAnnotation = "Annotation"
KeyArch = "Arch"
KeyArtifact = "Artifact"
KeyAuthFile = "AuthFile"
KeyAutoUpdate = "AutoUpdate"
KeyCertDir = "CertDir"
Expand Down Expand Up @@ -140,6 +143,7 @@ const (
KeyPolicy = "Policy"
KeyPublishPort = "PublishPort"
KeyPull = "Pull"
KeyQuiet = "Quiet"
KeyReadOnly = "ReadOnly"
KeyReadOnlyTmpfs = "ReadOnlyTmpfs"
KeyReloadCmd = "ReloadCmd"
Expand Down Expand Up @@ -214,6 +218,7 @@ var (
// Key: Extension
// Value: Processing order for resource naming dependencies
SupportedExtensions = map[string]int{
".artifact": 1,
".container": 4,
".volume": 2,
".kube": 4,
Expand Down Expand Up @@ -468,6 +473,29 @@ var (
KeyVolume: true,
},
},
ArtifactGroup: {
GroupName: ArtifactGroup,
XGroupName: XArtifactGroup,
SupportedKeys: map[string]bool{
KeyArch: true,
KeyArtifact: true,
KeyAuthFile: true,
KeyCertDir: true,
KeyContainersConfModule: true,
KeyCreds: true,
KeyDecryptionKey: true,
KeyGlobalArgs: true,
KeyOS: true,
KeyPolicy: true,
KeyPodmanArgs: true,
KeyQuiet: true,
KeyRetry: true,
KeyRetryDelay: true,
KeyServiceName: true,
KeyTLSVerify: true,
KeyVariant: true,
},
},
PodGroup: {
GroupName: PodGroup,
XGroupName: XPodGroup,
Expand Down Expand Up @@ -1483,6 +1511,10 @@ func GetBuildServiceName(podUnit *parser.UnitFile) string {
return getServiceName(podUnit, BuildGroup, "-build")
}

func GetArtifactServiceName(podUnit *parser.UnitFile) string {
return getServiceName(podUnit, ArtifactGroup, "-artifact")
}

func GetPodServiceName(podUnit *parser.UnitFile) string {
return getServiceName(podUnit, PodGroup, "-pod")
}
Expand Down Expand Up @@ -2025,10 +2057,11 @@ func resolveContainerMountParams(containerUnitFile, serviceUnitFile *parser.Unit

// Source resolution is required only for these types of mounts
sourceResultionRequired := map[string]struct{}{
"volume": {},
"bind": {},
"glob": {},
"image": {},
"volume": {},
"bind": {},
"glob": {},
"image": {},
"artifact": {},
}
if _, ok := sourceResultionRequired[mountType]; !ok {
return mount, nil
Expand Down Expand Up @@ -2282,3 +2315,51 @@ func initServiceUnitFile(quadletUnitFile *parser.UnitFile, isUser bool, unitsInf

return service, unitInfo, nil
}

func ConvertArtifact(artifact *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isUser bool) (*parser.UnitFile, error) {
service, unitInfo, err := initServiceUnitFile(artifact, isUser, unitsInfoMap, ArtifactGroup)
if err != nil {
return nil, err
}

artifactName, ok := artifact.Lookup(ArtifactGroup, KeyArtifact)
if !ok || len(artifactName) == 0 {
return nil, fmt.Errorf("no Artifact key specified")
}

podman := createBasePodmanCommand(artifact, ArtifactGroup)

podman.add("artifact", "pull")

stringKeys := map[string]string{
KeyArch: "--arch",
KeyAuthFile: "--authfile",
KeyCertDir: "--cert-dir",
KeyCreds: "--creds",
KeyDecryptionKey: "--decryption-key",
KeyOS: "--os",
KeyPolicy: "--policy",
KeyVariant: "--variant",
KeyRetry: "--retry",
KeyRetryDelay: "--retry-delay",
}
lookupAndAddString(artifact, ArtifactGroup, stringKeys, podman)

boolKeys := map[string]string{
KeyQuiet: "--quiet",
KeyTLSVerify: "--tls-verify",
}
lookupAndAddBoolean(artifact, ArtifactGroup, boolKeys, podman)

handlePodmanArgs(artifact, ArtifactGroup, podman)

podman.add(artifactName)

service.AddCmdline(ServiceGroup, "ExecStart", podman.Args)

defaultOneshotServiceGroup(service, true)

unitInfo.ResourceName = artifactName

return service, nil
}
24 changes: 24 additions & 0 deletions test/e2e/quadlet/artifact-mount.container
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args "--name" "systemd-%N"
## assert-podman-args "--mount"
## assert-podman-args "type=artifact,source=quay.io/libpod/testartifact:20250206-single,destination=/artifacts"
## assert-podman-args "--rm"
## assert-podman-args "--replace"
## assert-podman-args "-d"
## assert-podman-args "--cgroups=split"
## assert-podman-args "--sdnotify=conmon"
## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
## assert-key-is "Service" "KillMode" "mixed"
## assert-key-is "Service" "Delegate" "yes"
## assert-key-is "Service" "Type" "notify"
## assert-key-is "Service" "NotifyAccess" "all"
## assert-key-is "Service" "SyslogIdentifier" "%N"
## assert-key-is-regex "Service" "ExecStopPost" "-[/S].*/podman rm -v -f -i systemd-%N"
## assert-key-is-regex "Service" "ExecStop" ".*/podman rm -v -f -i systemd-%N"
## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n"
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service"
## assert-key-is-regex "Unit" "Wants" "network-online.target|podman-user-wait-network-online.service"

[Container]
Image=localhost/imagename
Mount=type=artifact,source=quay.io/libpod/testartifact:20250206-single,destination=/artifacts
10 changes: 10 additions & 0 deletions test/e2e/quadlet/basic.artifact
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## assert-podman-final-args quay.io/libpod/testartifact:20250206-single
## assert-podman-args "artifact"
## assert-podman-args "pull"
## assert-key-is "Service" "Type" "oneshot"
## assert-key-is "Service" "RemainAfterExit" "yes"
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service"
## assert-key-is-regex "Unit" "Wants" "network-online.target|podman-user-wait-network-online.service"

[Artifact]
Artifact=quay.io/libpod/testartifact:20250206-single
Loading