Skip to content

Commit 34254cd

Browse files
committed
Add artifact quadlet unit type support
RFE: Add artifact quadlet unit type #25778 Signed-off-by: Odilon Sousa <osousa@redhat.com>
1 parent 1671029 commit 34254cd

File tree

11 files changed

+368
-11
lines changed

11 files changed

+368
-11
lines changed

cmd/quadlet/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ func generateUnitsInfoMap(units []*parser.UnitFile) map[string]*quadlet.UnitInfo
396396
// types), but still breaks the dependency cycle between .volume and .build ([Volume] can
397397
// have Image=some.build, and [Build] can have Volume=some.volume:/some-volume)
398398
resourceName = quadlet.GetBuiltImageName(unit)
399+
case strings.HasSuffix(unit.Filename, ".artifact"):
400+
serviceName = quadlet.GetArtifactServiceName(unit)
399401
case strings.HasSuffix(unit.Filename, ".pod"):
400402
containers = make([]string, 0)
401403
// Prefill resouceNames for .pod files.
@@ -550,6 +552,9 @@ func process() bool {
550552
service, err = quadlet.ConvertImage(unit, unitsInfoMap, isUserFlag)
551553
case strings.HasSuffix(unit.Filename, ".build"):
552554
service, warnings, err = quadlet.ConvertBuild(unit, unitsInfoMap, isUserFlag)
555+
case strings.HasSuffix(unit.Filename, ".artifact"):
556+
warnIfAmbiguousName(unit, quadlet.ArtifactGroup)
557+
service, err = quadlet.ConvertArtifact(unit, unitsInfoMap, isUserFlag)
553558
case strings.HasSuffix(unit.Filename, ".pod"):
554559
service, warnings, err = quadlet.ConvertPod(unit, unit.Filename, unitsInfoMap, isUserFlag)
555560
default:

docs/source/markdown/podman-systemd.unit.5.md

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ podman\-systemd.unit - systemd units using Podman Quadlet
66

77
## SYNOPSIS
88

9-
*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image, *name*.build *name*.pod
9+
*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image, *name*.build *name*.pod, *name*.artifact
1010

1111
### Podman rootful unit search path
1212

@@ -48,7 +48,7 @@ the [Service] table and [Install] tables pass directly to systemd and are handle
4848
See systemd.unit(5) man page for more information.
4949

5050
The Podman generator reads the search paths above and reads files with the extensions `.container`
51-
`.volume`, `.network`, `.build`, `.pod` and `.kube`, and for each file generates a similarly named `.service` file. Be aware that
51+
`.volume`, `.network`, `.build`, `.pod`, `.kube`, and `.artifact`, and for each file generates a similarly named `.service` file. Be aware that
5252
existing vendor services (i.e., in `/usr/`) are replaced if they have the same name. The generated unit files can
5353
be started and managed with `systemctl` like any other systemd service. `systemctl {--user} list-unit-files`
5454
lists existing unit files on the system.
@@ -104,7 +104,7 @@ Quadlet requires the use of cgroup v2, use `podman info --format {{.Host.Cgroups
104104

105105
By default, the `Type` field of the `Service` section of the Quadlet file does not need to be set.
106106
Quadlet will set it to `notify` for `.container` and `.kube` files,
107-
`forking` for `.pod` files, and `oneshot` for `.volume`, `.network`, `.build`, and `.image` files.
107+
`forking` for `.pod` files, and `oneshot` for `.volume`, `.network`, `.build`, `.image`, and `.artifact` files.
108108

109109
However, `Type` may be explicitly set to `oneshot` for `.container` and `.kube` files when no containers are expected
110110
to run once `podman` exits.
@@ -2091,6 +2091,123 @@ Override the default architecture variant of the container image.
20912091

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

2094+
## Artifact units [Artifact]
2095+
2096+
### WARNING: Experimental Unit
2097+
2098+
This unit is considered experimental and still in development. Inputs, options, and outputs are all subject to change.
2099+
2100+
Artifact units are named with a `.artifact` extension and contain a `[Artifact]` section describing
2101+
the container artifact pull command. The generated service is a one-time command that ensures that the artifact
2102+
exists on the host, pulling it if needed.
2103+
2104+
Using artifact units allows containers to depend on artifacts being automatically pulled. This is
2105+
particularly useful for managing artifacts that containers need to mount or access, the **Artifact** key is mandatory inside of the [Artifact] unit.
2106+
2107+
Valid options for `[Artifact]` are listed below:
2108+
2109+
| **[Artifact] options** | **podman artifact pull equivalent** |
2110+
|---------------------------------------------|--------------------------------------------------------|
2111+
| Artifact=quay\.io/foobar/artifact:special | podman artifact pull quay\.io/foobar/artifact:special |
2112+
| AuthFile=/etc/registry/auth\.json | --authfile=/etc/registry/auth\.json |
2113+
| CertDir=/etc/registry/certs | --cert-dir=/etc/registry/certs |
2114+
| ContainersConfModule=/etc/nvd\.conf | --module=/etc/nvd\.conf |
2115+
| Creds=username:password | --creds=username:password |
2116+
| DecryptionKey=/etc/registry\.key | --decryption-key=/etc/registry\.key |
2117+
| GlobalArgs=--log-level=debug | --log-level=debug |
2118+
| PodmanArgs=--pull never | --pull never |
2119+
| Quiet=true | --quiet |
2120+
| Retry=5 | --retry=5 |
2121+
| RetryDelay=10s | --retry-delay=10s |
2122+
| ServiceName=my-artifact | Set the systemd service name to my-artifact.service |
2123+
| TLSVerify=false | --tls-verify=false |
2124+
2125+
### `Artifact=`
2126+
2127+
The artifact to pull from a registry onto the local machine. This is the only required key for artifact units.
2128+
2129+
It is required to use a fully qualified artifact name rather than a short name, both for
2130+
performance and robustness reasons.
2131+
2132+
### `AuthFile=`
2133+
2134+
Path of the authentication file.
2135+
2136+
This is equivalent to the Podman `--authfile` option.
2137+
2138+
### `CertDir=`
2139+
2140+
Use certificates at path (*.crt, *.cert, *.key) to connect to the registry.
2141+
2142+
This is equivalent to the Podman `--cert-dir` option.
2143+
2144+
### `ContainersConfModule=`
2145+
2146+
Load the specified containers.conf(5) module. Equivalent to the Podman `--module` option.
2147+
2148+
This key can be listed multiple times.
2149+
2150+
### `Creds=`
2151+
2152+
The credentials to use when contacting the registry in the format `[username[:password]]`.
2153+
2154+
This is equivalent to the Podman `--creds` option.
2155+
2156+
### `DecryptionKey=`
2157+
2158+
The `[key[:passphrase]]` to be used for decryption of artifacts.
2159+
2160+
This is equivalent to the Podman `--decryption-key` option.
2161+
2162+
### `GlobalArgs=`
2163+
2164+
This key contains a list of arguments passed directly between `podman` and `artifact`
2165+
in the generated file. It can be used to access Podman features otherwise unsupported by the generator. Since the generator is unaware
2166+
of what unexpected interactions can be caused by these arguments, it is not recommended to use
2167+
this option.
2168+
2169+
The format of this is a space separated list of arguments, which can optionally be individually
2170+
escaped to allow inclusion of whitespace and other control characters.
2171+
2172+
This key can be listed multiple times.
2173+
2174+
### `PodmanArgs=`
2175+
2176+
This key contains a list of arguments passed directly to the end of the `podman artifact pull` command
2177+
in the generated file (right before the artifact name in the command line). It can be used to
2178+
access Podman features otherwise unsupported by the generator. Since the generator is unaware
2179+
of what unexpected interactions can be caused by these arguments, it is not recommended to use
2180+
this option.
2181+
2182+
The format of this is a space separated list of arguments, which can optionally be individually
2183+
escaped to allow inclusion of whitespace and other control characters.
2184+
2185+
This key can be listed multiple times.
2186+
2187+
### `Quiet=`
2188+
2189+
Suppress output information when pulling artifacts.
2190+
2191+
This is equivalent to the Podman `--quiet` option.
2192+
2193+
### `Retry=`
2194+
2195+
Number of times to retry the artifact pull when a HTTP error occurs. Equivalent to the Podman `--retry` option.
2196+
2197+
### `RetryDelay=`
2198+
2199+
Delay between retries. Equivalent to the Podman `--retry-delay` option.
2200+
2201+
### `ServiceName=`
2202+
2203+
The (optional) name of the systemd service. If this is not specified, the default value is the same name as the unit, but with a `-artifact` suffix, i.e. a `$name.artifact` file creates a `$name-artifact.service` systemd service.
2204+
2205+
### `TLSVerify=`
2206+
2207+
Require HTTPS and verification of certificates when contacting registries.
2208+
2209+
This is equivalent to the Podman `--tls-verify` option.
2210+
20942211
## Quadlet section [Quadlet]
20952212
Some quadlet specific configuration is shared between different unit types. Those settings
20962213
can be configured in the `[Quadlet]` section.
@@ -2195,6 +2312,29 @@ IPRange=172.16.0.0/28
21952312
Label=org.test.Key=value
21962313
```
21972314

2315+
Example `test.artifact` to only pull the artifact using one auth file:
2316+
```
2317+
[Artifact]
2318+
Artifact=quay.io/example/my-artifact:latest
2319+
AuthFile=/etc/registry/auth.json
2320+
TLSVerify=false
2321+
```
2322+
2323+
Example usage where a container depends on an artifact:
2324+
2325+
`my-artifact.artifact`:
2326+
```
2327+
[Artifact]
2328+
Artifact=quay.io/example/my-config:latest
2329+
```
2330+
2331+
`my-app.container`:
2332+
```
2333+
[Container]
2334+
Image=quay.io/example/my-app:latest
2335+
Mount=type=artifact,source=my-artifact.artifact,destination=/etc/config
2336+
```
2337+
21982338
Example for Container in a Pod:
21992339

22002340
`test.pod`

pkg/systemd/quadlet/quadlet.go

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
UnitDirDistro = "/usr/share/containers/systemd"
2828

2929
// Names of commonly used systemd/quadlet group names
30+
ArtifactGroup = "Artifact"
3031
ContainerGroup = "Container"
3132
InstallGroup = "Install"
3233
KubeGroup = "Kube"
@@ -38,6 +39,7 @@ const (
3839
ImageGroup = "Image"
3940
BuildGroup = "Build"
4041
QuadletGroup = "Quadlet"
42+
XArtifactGroup = "X-Artifact"
4143
XContainerGroup = "X-Container"
4244
XKubeGroup = "X-Kube"
4345
XNetworkGroup = "X-Network"
@@ -61,6 +63,7 @@ const (
6163
KeyAllTags = "AllTags"
6264
KeyAnnotation = "Annotation"
6365
KeyArch = "Arch"
66+
KeyArtifact = "Artifact"
6467
KeyAuthFile = "AuthFile"
6568
KeyAutoUpdate = "AutoUpdate"
6669
KeyCertDir = "CertDir"
@@ -141,6 +144,7 @@ const (
141144
KeyPolicy = "Policy"
142145
KeyPublishPort = "PublishPort"
143146
KeyPull = "Pull"
147+
KeyQuiet = "Quiet"
144148
KeyReadOnly = "ReadOnly"
145149
KeyReadOnlyTmpfs = "ReadOnlyTmpfs"
146150
KeyReloadCmd = "ReloadCmd"
@@ -458,6 +462,25 @@ var (
458462
KeyVolume: true,
459463
},
460464
},
465+
ArtifactGroup: {
466+
GroupName: ArtifactGroup,
467+
XGroupName: XArtifactGroup,
468+
SupportedKeys: map[string]bool{
469+
KeyArtifact: true,
470+
KeyAuthFile: true,
471+
KeyCertDir: true,
472+
KeyContainersConfModule: true,
473+
KeyCreds: true,
474+
KeyDecryptionKey: true,
475+
KeyGlobalArgs: true,
476+
KeyPodmanArgs: true,
477+
KeyQuiet: true,
478+
KeyRetry: true,
479+
KeyRetryDelay: true,
480+
KeyServiceName: true,
481+
KeyTLSVerify: true,
482+
},
483+
},
461484
PodGroup: {
462485
GroupName: PodGroup,
463486
XGroupName: XPodGroup,
@@ -1464,6 +1487,8 @@ func GetUnitServiceName(unit *parser.UnitFile) (string, error) {
14641487
return GetImageServiceName(unit), nil
14651488
case strings.HasSuffix(unit.Filename, ".build"):
14661489
return GetBuildServiceName(unit), nil
1490+
case strings.HasSuffix(unit.Filename, ".artifact"):
1491+
return GetArtifactServiceName(unit), nil
14671492
case strings.HasSuffix(unit.Filename, ".pod"):
14681493
return GetPodServiceName(unit), nil
14691494
default:
@@ -1495,6 +1520,10 @@ func GetBuildServiceName(podUnit *parser.UnitFile) string {
14951520
return getServiceName(podUnit, BuildGroup, "-build")
14961521
}
14971522

1523+
func GetArtifactServiceName(podUnit *parser.UnitFile) string {
1524+
return getServiceName(podUnit, ArtifactGroup, "-artifact")
1525+
}
1526+
14981527
func GetPodServiceName(podUnit *parser.UnitFile) string {
14991528
return getServiceName(podUnit, PodGroup, "-pod")
15001529
}
@@ -1871,7 +1900,7 @@ func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, sour
18711900
if source[0] == '/' {
18721901
// Absolute path
18731902
serviceUnitFile.Add(UnitGroup, "RequiresMountsFor", source)
1874-
} else if strings.HasSuffix(source, ".volume") || (checkImage && strings.HasSuffix(source, ".image")) {
1903+
} else if strings.HasSuffix(source, ".volume") || (checkImage && strings.HasSuffix(source, ".image")) || strings.HasSuffix(source, ".artifact") {
18751904
sourceUnitInfo, ok := unitsInfoMap[source]
18761905
if !ok {
18771906
return "", fmt.Errorf("requested Quadlet source %s was not found", source)
@@ -2040,10 +2069,11 @@ func resolveContainerMountParams(containerUnitFile, serviceUnitFile *parser.Unit
20402069

20412070
// Source resolution is required only for these types of mounts
20422071
sourceResultionRequired := map[string]struct{}{
2043-
"volume": {},
2044-
"bind": {},
2045-
"glob": {},
2046-
"image": {},
2072+
"volume": {},
2073+
"bind": {},
2074+
"glob": {},
2075+
"image": {},
2076+
"artifact": {},
20472077
}
20482078
if _, ok := sourceResultionRequired[mountType]; !ok {
20492079
return mount, nil
@@ -2297,3 +2327,47 @@ func initServiceUnitFile(quadletUnitFile *parser.UnitFile, isUser bool, unitsInf
22972327

22982328
return service, unitInfo, nil
22992329
}
2330+
2331+
func ConvertArtifact(artifact *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isUser bool) (*parser.UnitFile, error) {
2332+
service, unitInfo, err := initServiceUnitFile(artifact, isUser, unitsInfoMap, ArtifactGroup)
2333+
if err != nil {
2334+
return nil, err
2335+
}
2336+
2337+
artifactName, ok := artifact.Lookup(ArtifactGroup, KeyArtifact)
2338+
if !ok || len(artifactName) == 0 {
2339+
return nil, fmt.Errorf("no Artifact key specified")
2340+
}
2341+
2342+
podman := createBasePodmanCommand(artifact, ArtifactGroup)
2343+
2344+
podman.add("artifact", "pull")
2345+
2346+
stringKeys := map[string]string{
2347+
KeyAuthFile: "--authfile",
2348+
KeyCertDir: "--cert-dir",
2349+
KeyCreds: "--creds",
2350+
KeyDecryptionKey: "--decryption-key",
2351+
KeyRetry: "--retry",
2352+
KeyRetryDelay: "--retry-delay",
2353+
}
2354+
lookupAndAddString(artifact, ArtifactGroup, stringKeys, podman)
2355+
2356+
boolKeys := map[string]string{
2357+
KeyQuiet: "--quiet",
2358+
KeyTLSVerify: "--tls-verify",
2359+
}
2360+
lookupAndAddBoolean(artifact, ArtifactGroup, boolKeys, podman)
2361+
2362+
handlePodmanArgs(artifact, ArtifactGroup, podman)
2363+
2364+
podman.add(artifactName)
2365+
2366+
service.AddCmdline(ServiceGroup, "ExecStart", podman.Args)
2367+
2368+
defaultOneshotServiceGroup(service, true)
2369+
2370+
unitInfo.ResourceName = artifactName
2371+
2372+
return service, nil
2373+
}

pkg/systemd/quadlet/quadlet_common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var (
44
// Key: Extension
55
// Value: Processing order for resource naming dependencies
66
SupportedExtensions = map[string]int{
7+
".artifact": 1,
78
".container": 4,
89
".volume": 2,
910
".kube": 4,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
## assert-podman-final-args localhost/imagename
2+
## assert-podman-args "--name" "systemd-%N"
3+
## assert-podman-args "--mount"
4+
## assert-podman-args "type=artifact,source=quay.io/libpod/testartifact:20250206-single,destination=/artifacts"
5+
## assert-podman-args "--rm"
6+
## assert-podman-args "--replace"
7+
## assert-podman-args "-d"
8+
## assert-podman-args "--cgroups=split"
9+
## assert-podman-args "--sdnotify=conmon"
10+
## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
11+
## assert-key-is "Service" "KillMode" "mixed"
12+
## assert-key-is "Service" "Delegate" "yes"
13+
## assert-key-is "Service" "Type" "notify"
14+
## assert-key-is "Service" "NotifyAccess" "all"
15+
## assert-key-is "Service" "SyslogIdentifier" "%N"
16+
## assert-key-is-regex "Service" "ExecStopPost" "-[/S].*/podman rm -v -f -i systemd-%N"
17+
## assert-key-is-regex "Service" "ExecStop" ".*/podman rm -v -f -i systemd-%N"
18+
## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n"
19+
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service"
20+
## assert-key-is-regex "Unit" "Wants" "network-online.target|podman-user-wait-network-online.service"
21+
22+
[Container]
23+
Image=localhost/imagename
24+
Mount=type=artifact,source=quay.io/libpod/testartifact:20250206-single,destination=/artifacts

test/e2e/quadlet/basic.artifact

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## assert-podman-final-args quay.io/libpod/testartifact:20250206-single
2+
## assert-podman-args "artifact"
3+
## assert-podman-args "pull"
4+
## assert-key-is "Service" "Type" "oneshot"
5+
## assert-key-is "Service" "RemainAfterExit" "yes"
6+
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service"
7+
## assert-key-is-regex "Unit" "Wants" "network-online.target|podman-user-wait-network-online.service"
8+
9+
[Artifact]
10+
Artifact=quay.io/libpod/testartifact:20250206-single

0 commit comments

Comments
 (0)