diff --git a/cmd/quadlet/main.go b/cmd/quadlet/main.go index dfd1260ff6..6896772e1c 100644 --- a/cmd/quadlet/main.go +++ b/cmd/quadlet/main.go @@ -396,6 +396,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"): containers = make([]string, 0) // Prefill resouceNames for .pod files. @@ -550,6 +552,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: diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md index 480f08eb22..73b7bab0ba 100644 --- a/docs/source/markdown/podman-systemd.unit.5.md +++ b/docs/source/markdown/podman-systemd.unit.5.md @@ -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 @@ -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. @@ -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. @@ -2091,6 +2091,123 @@ 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. + +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, the **Artifact** key is mandatory inside of the [Artifact] unit. + +Valid options for `[Artifact]` are listed below: + +| **[Artifact] options** | **podman artifact pull equivalent** | +|---------------------------------------------|--------------------------------------------------------| +| 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 | +| ContainersConfModule=/etc/nvd\.conf | --module=/etc/nvd\.conf | +| Creds=username:password | --creds=username:password | +| DecryptionKey=/etc/registry\.key | --decryption-key=/etc/registry\.key | +| GlobalArgs=--log-level=debug | --log-level=debug | +| PodmanArgs=--pull never | --pull never | +| Quiet=true | --quiet | +| Retry=5 | --retry=5 | +| RetryDelay=10s | --retry-delay=10s | +| ServiceName=my-artifact | Set the systemd service name to my-artifact.service | +| TLSVerify=false | --tls-verify=false | + +### `Artifact=` + +The artifact to pull from a registry onto the local machine. This is the only required key for artifact units. + +It is required 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. + +### `ContainersConfModule=` + +Load the specified containers.conf(5) module. Equivalent to the Podman `--module` option. + +This key can be listed multiple times. + +### `Creds=` + +The credentials to use when contacting the registry in the format `[username[:password]]`. + +This is equivalent to the Podman `--creds` 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. + +### `PodmanArgs=` + +This key contains a list of arguments passed directly to the end of the `podman artifact pull` 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. + +### `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. + +### `ServiceName=` + +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. + +### `TLSVerify=` + +Require HTTPS and verification of certificates when contacting registries. + +This is equivalent to the Podman `--tls-verify` option. + ## Quadlet section [Quadlet] Some quadlet specific configuration is shared between different unit types. Those settings can be configured in the `[Quadlet]` section. @@ -2195,6 +2312,29 @@ IPRange=172.16.0.0/28 Label=org.test.Key=value ``` +Example `test.artifact` to only pull the artifact using one auth file: +``` +[Artifact] +Artifact=quay.io/example/my-artifact:latest +AuthFile=/etc/registry/auth.json +TLSVerify=false +``` + +Example usage where a container depends on an artifact: + +`my-artifact.artifact`: +``` +[Artifact] +Artifact=quay.io/example/my-config:latest +``` + +`my-app.container`: +``` +[Container] +Image=quay.io/example/my-app:latest +Mount=type=artifact,source=my-artifact.artifact,destination=/etc/config +``` + Example for Container in a Pod: `test.pod` diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index 575abe0774..90fc520f98 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -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" @@ -38,6 +39,7 @@ const ( ImageGroup = "Image" BuildGroup = "Build" QuadletGroup = "Quadlet" + XArtifactGroup = "X-Artifact" XContainerGroup = "X-Container" XKubeGroup = "X-Kube" XNetworkGroup = "X-Network" @@ -61,6 +63,7 @@ const ( KeyAllTags = "AllTags" KeyAnnotation = "Annotation" KeyArch = "Arch" + KeyArtifact = "Artifact" KeyAuthFile = "AuthFile" KeyAutoUpdate = "AutoUpdate" KeyCertDir = "CertDir" @@ -141,6 +144,7 @@ const ( KeyPolicy = "Policy" KeyPublishPort = "PublishPort" KeyPull = "Pull" + KeyQuiet = "Quiet" KeyReadOnly = "ReadOnly" KeyReadOnlyTmpfs = "ReadOnlyTmpfs" KeyReloadCmd = "ReloadCmd" @@ -458,6 +462,25 @@ var ( KeyVolume: true, }, }, + ArtifactGroup: { + GroupName: ArtifactGroup, + XGroupName: XArtifactGroup, + SupportedKeys: map[string]bool{ + KeyArtifact: true, + KeyAuthFile: true, + KeyCertDir: true, + KeyContainersConfModule: true, + KeyCreds: true, + KeyDecryptionKey: true, + KeyGlobalArgs: true, + KeyPodmanArgs: true, + KeyQuiet: true, + KeyRetry: true, + KeyRetryDelay: true, + KeyServiceName: true, + KeyTLSVerify: true, + }, + }, PodGroup: { GroupName: PodGroup, XGroupName: XPodGroup, @@ -1464,6 +1487,8 @@ func GetUnitServiceName(unit *parser.UnitFile) (string, error) { return GetImageServiceName(unit), nil case strings.HasSuffix(unit.Filename, ".build"): return GetBuildServiceName(unit), nil + case strings.HasSuffix(unit.Filename, ".artifact"): + return GetArtifactServiceName(unit), nil case strings.HasSuffix(unit.Filename, ".pod"): return GetPodServiceName(unit), nil default: @@ -1495,6 +1520,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") } @@ -1871,7 +1900,7 @@ func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, sour if source[0] == '/' { // Absolute path serviceUnitFile.Add(UnitGroup, "RequiresMountsFor", source) - } else if strings.HasSuffix(source, ".volume") || (checkImage && strings.HasSuffix(source, ".image")) { + } else if strings.HasSuffix(source, ".volume") || (checkImage && strings.HasSuffix(source, ".image")) || strings.HasSuffix(source, ".artifact") { sourceUnitInfo, ok := unitsInfoMap[source] if !ok { return "", fmt.Errorf("requested Quadlet source %s was not found", source) @@ -2040,10 +2069,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 @@ -2297,3 +2327,47 @@ 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{ + KeyAuthFile: "--authfile", + KeyCertDir: "--cert-dir", + KeyCreds: "--creds", + KeyDecryptionKey: "--decryption-key", + 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 +} diff --git a/pkg/systemd/quadlet/quadlet_common.go b/pkg/systemd/quadlet/quadlet_common.go index b6100fcaf6..05087b678e 100644 --- a/pkg/systemd/quadlet/quadlet_common.go +++ b/pkg/systemd/quadlet/quadlet_common.go @@ -4,6 +4,7 @@ var ( // Key: Extension // Value: Processing order for resource naming dependencies SupportedExtensions = map[string]int{ + ".artifact": 1, ".container": 4, ".volume": 2, ".kube": 4, diff --git a/test/e2e/quadlet/artifact-mount.container b/test/e2e/quadlet/artifact-mount.container new file mode 100644 index 0000000000..bc8eab31ab --- /dev/null +++ b/test/e2e/quadlet/artifact-mount.container @@ -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 diff --git a/test/e2e/quadlet/basic.artifact b/test/e2e/quadlet/basic.artifact new file mode 100644 index 0000000000..82d4037940 --- /dev/null +++ b/test/e2e/quadlet/basic.artifact @@ -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 diff --git a/test/e2e/quadlet/mount.container b/test/e2e/quadlet/mount.container index 0f431c1ee2..b0a4713785 100644 --- a/test/e2e/quadlet/mount.container +++ b/test/e2e/quadlet/mount.container @@ -9,8 +9,6 @@ Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true ## assert-podman-args-key-val "--mount" "," "type=volume,source=vol1,destination=/path/in/container,ro=true" Mount=type=volume,source=vol1,destination=/path/in/container,ro=true ## assert-podman-args-key-val "--mount" "," "type=volume,source=systemd-basic,destination=/path/in/container,ro=true" -## assert-key-is "Unit" "Requires" "basic-volume.service" "basic-image.service" -## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-volume.service" "basic-image.service" Mount=type=volume,source=basic.volume,destination=/path/in/container,ro=true ## assert-podman-args-key-val "--mount" "," "type=tmpfs,tmpfs-size=512M,destination=/path/in/container" Mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container @@ -18,6 +16,8 @@ Mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container Mount=type=image,source=fedora,destination=/fedora-image,rw=true ## assert-podman-args-key-val "--mount" "," "type=image,source=localhost/imagename,destination=/fedora-image,rw=true" Mount=type=image,source=basic.image,destination=/fedora-image,rw=true +## assert-podman-args-key-val "--mount" "," "type=artifact,source=quay.io/libpod/testartifact:20250206-single,destination=/artifacts" +Mount=type=artifact,source=basic.artifact,destination=/artifacts ## assert-podman-args-key-val "--mount" "," "type=devpts,destination=/dev/pts" Mount=type=devpts,destination=/dev/pts ## assert-podman-args-key-val-regex "--mount" "," "type=bind,source=.*/podman-e2e-.*/subtest-.*/quadlet/path/on/host,destination=/path/in/container" @@ -28,3 +28,5 @@ Mount=type=volume,source=vol1,destination=/path/in/container,ro Mount=type=bind,src=/tmp,\"dst=/path,1\" ## assert-podman-args-key-val-regex "--mount" "," "type=bind,source=.*/podman-e2e-.*/subtest-.*/quadlet/src,destination=/dst/,idmap=uids=12-34-1;gids=56-78-1" Mount=type=bind,source=./src/,destination=/dst/,idmap=uids=12-34-1;gids=56-78-1 +## assert-key-is "Unit" "Requires" "basic-volume.service" "basic-image.service" "basic-artifact.service" +## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-volume.service" "basic-image.service" "basic-artifact.service" diff --git a/test/e2e/quadlet/options.artifact b/test/e2e/quadlet/options.artifact new file mode 100644 index 0000000000..9c8e789fe8 --- /dev/null +++ b/test/e2e/quadlet/options.artifact @@ -0,0 +1,23 @@ +## assert-podman-final-args quay.io/libpod/testartifact:20250206-single +## assert-podman-args "artifact" +## assert-podman-args "pull" +## assert-podman-args "--authfile" +## assert-podman-args "/tmp/auth.json" +## assert-podman-args "--cert-dir" +## assert-podman-args "/tmp/certs" +## assert-podman-args "--quiet" +## assert-podman-args "--retry" +## assert-podman-args "3" +## assert-podman-args "--tls-verify=false" +## 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 +AuthFile=/tmp/auth.json +CertDir=/tmp/certs +Quiet=true +Retry=3 +TLSVerify=false diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 7aaf5f646e..2d33d49bbd 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -51,6 +51,8 @@ func calcServiceName(path string) string { service += "-image" case ".build": service += "-build" + case ".artifact": + service += "-artifact" case ".pod": service += "-pod" } @@ -1076,6 +1078,9 @@ BOGUS=foo Entry("Build - Retry", "retry.build"), Entry("Build - No WorkingDirectory with systemd specifier", "no-workingdirectory-systemd-specifier.build"), + Entry("Artifact - Basic", "basic.artifact"), + Entry("Artifact - Options", "options.artifact"), + Entry("Pod - Basic", "basic.pod"), Entry("Pod - DNS", "dns.pod"), Entry("Pod - DNS Option", "dns-option.pod"), @@ -1171,13 +1176,14 @@ BOGUS=foo runSuccessQuadletTestCase(fileName) }, - Entry("Container - Mount", "mount.container", []string{"basic.image", "basic.volume"}), + Entry("Container - Mount", "mount.container", []string{"basic.image", "basic.volume", "basic.artifact"}), Entry("Container - Quadlet Network", "network.quadlet.container", []string{"basic.network"}), Entry("Container - Quadlet Volume", "volume.container", []string{"basic.volume"}), Entry("Container - Mount overriding service name", "mount.servicename.container", []string{"service-name.volume"}), Entry("Container - Quadlet Network overriding service name", "network.quadlet.servicename.container", []string{"service-name.network"}), Entry("Container - Quadlet Volume overriding service name", "volume.servicename.container", []string{"service-name.volume"}), Entry("Container - Quadlet build with multiple tags", "build.multiple-tags.container", []string{"multiple-tags.build"}), + Entry("Container - Artifact Mount", "artifact-mount.container", []string{"basic.artifact"}), Entry("Container - Reuse another container's network", "network.reuse.container", []string{"basic.container"}), Entry("Container - Reuse another named container's network", "network.reuse.name.container", []string{"name.container"}), Entry("Container - Reuse another container's network", "a.network.reuse.container", []string{"basic.container"}), @@ -1186,6 +1192,7 @@ BOGUS=foo "Container - Dependency between quadlet units", "dependent.container", []string{ + "basic.artifact", "basic.build", "basic.container", "basic.image", @@ -1239,6 +1246,7 @@ BOGUS=foo "Build - Dependency between quadlet units", "dependent.build", []string{ + "basic.artifact", "basic.build", "basic.container", "basic.image", @@ -1258,6 +1266,7 @@ BOGUS=foo "Pod - Dependency between quadlet units", "dependent.pod", []string{ + "basic.artifact", "basic.build", "basic.container", "basic.image", @@ -1272,6 +1281,7 @@ BOGUS=foo "Image - Dependency between quadlet units", "dependent.image", []string{ + "basic.artifact", "basic.build", "basic.container", "basic.image", @@ -1286,6 +1296,7 @@ BOGUS=foo "Network - Dependency between quadlet units", "dependent.network", []string{ + "basic.artifact", "basic.build", "basic.container", "basic.image", diff --git a/test/system/252-quadlet.bats b/test/system/252-quadlet.bats index 85b6e942eb..bfce12d49c 100644 --- a/test/system/252-quadlet.bats +++ b/test/system/252-quadlet.bats @@ -1856,6 +1856,71 @@ EOF done < <(parse_table "${dropin_files}") } +@test "quadlet - artifact" { + local quadlet_tmpdir=$PODMAN_TMPDIR/quadlets + + local registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT} + local artifact_for_test=$registry/test-artifact:$(random_string) + local authfile=$PODMAN_TMPDIR/authfile.json + + # Create a test artifact file + local test_artifact_dir=$PODMAN_TMPDIR/test-artifact + mkdir -p $test_artifact_dir + echo "test artifact content $(random_string)" > $test_artifact_dir/test-file.txt + + # In order to test artifact pull but without possible Network issues, + # this test uses an additional registry. + # 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 + + # Create and push a test artifact to the registry + run_podman artifact add $artifact_for_test $test_artifact_dir/test-file.txt + run_podman artifact push --tls-verify=false --authfile=$authfile $artifact_for_test + + # Remove the local artifact to make sure it will be pulled again + run_podman artifact rm $artifact_for_test + + # Create artifact quadlet file + local artifact_file=$PODMAN_TMPDIR/test-artifact.artifact + cat >$artifact_file << EOF +[Artifact] +Artifact=$artifact_for_test +AuthFile=$authfile +TLSVerify=false +EOF + + run_quadlet "$artifact_file" + service_setup $QUADLET_SERVICE_NAME + + # Wait for the service to complete (it's a oneshot service) + local timeout=30 + local count=0 + while [ $count -lt $timeout ]; do + run systemctl show --value --property=ActiveState "$QUADLET_SERVICE_NAME" + if [ "$output" = "active" ]; then + break + fi + sleep 1 + count=$((count + 1)) + done + + # Verify artifact was pulled + run_podman artifact ls + assert $status -eq 0 "Failed to list artifacts" + # Extract the repository and tag parts separately since artifact ls shows them in columns + local artifact_repo=$(echo "$artifact_for_test" | cut -d: -f1) + local artifact_tag=$(echo "$artifact_for_test" | cut -d: -f2) + assert "$output" =~ "$artifact_repo.*$artifact_tag" "Artifact should exist after quadlet service runs" + + # Clean up + run_podman artifact rm $artifact_for_test +} + # Following issue: https://github.com/containers/podman/issues/24599 # Make sure future changes do not break @test "quadlet - build with pull" { diff --git a/test/system/helpers.systemd.bash b/test/system/helpers.systemd.bash index 6ca33c1d11..be7586e147 100644 --- a/test/system/helpers.systemd.bash +++ b/test/system/helpers.systemd.bash @@ -126,6 +126,8 @@ quadlet_to_service_name() { suffix="-pod" elif [ "$extension" == "build" ]; then suffix="-build" + elif [ "$extension" == "artifact" ]; then + suffix="-artifact" fi echo "$filename$suffix.service"