From 0179aa2451d114a26f8111a81934f75fe6fbf64d Mon Sep 17 00:00:00 2001
From: Alexander Larsson <alexl@redhat.com>
Date: Tue, 20 Dec 2022 15:53:26 +0100
Subject: [PATCH] quadlet: Support Type=oneshot container files

These just run once and are considered successful at exist. Not much is
needed to support it, but we have to avoid overwriting the type
with Type=notify.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
---
 pkg/systemd/quadlet/quadlet.go        | 32 +++++++++++++++++----------
 test/e2e/quadlet/basepodman.container |  2 +-
 test/e2e/quadlet/oneshot.container    |  9 ++++++++
 test/e2e/quadlet_test.go              |  1 +
 test/system/252-quadlet.bats          | 23 +++++++++++++++++++
 5 files changed, 54 insertions(+), 13 deletions(-)
 create mode 100644 test/e2e/quadlet/oneshot.container

diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go
index 611e495085..881070a32d 100644
--- a/pkg/systemd/quadlet/quadlet.go
+++ b/pkg/systemd/quadlet/quadlet.go
@@ -274,9 +274,6 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
 		// On clean shutdown, remove container
 		"--rm",
 
-		// Detach from container, we don't need the podman process to hang around
-		"-d",
-
 		// But we still want output to the journal, so use the log driver.
 		"--log-driver", "passthrough",
 	)
@@ -300,16 +297,27 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
 		podman.addBool("--init", runInit)
 	}
 
-	// By default we handle startup notification with conmon, but allow passing it to the container with Notify=yes
-	notify := container.LookupBooleanWithDefault(ContainerGroup, KeyNotify, false)
-	if notify {
-		podman.add("--sdnotify=container")
-	} else {
-		podman.add("--sdnotify=conmon")
+	serviceType, ok := service.Lookup(ServiceGroup, "Type")
+	if ok && serviceType != "notify" && serviceType != "oneshot" {
+		return nil, fmt.Errorf("invalid service Type '%s'", serviceType)
+	}
+
+	if serviceType != "oneshot" {
+		// If we're not in oneshot mode always use some form of sd-notify, normally via conmon,
+		// but we also allow passing it to the container by setting Notify=yes
+		notify := container.LookupBooleanWithDefault(ContainerGroup, KeyNotify, false)
+		if notify {
+			podman.add("--sdnotify=container")
+		} else {
+			podman.add("--sdnotify=conmon")
+		}
+		service.Setv(ServiceGroup,
+			"Type", "notify",
+			"NotifyAccess", "all")
+
+		// Detach from container, we don't need the podman process to hang around
+		podman.add("-d")
 	}
-	service.Setv(ServiceGroup,
-		"Type", "notify",
-		"NotifyAccess", "all")
 
 	if !container.HasKey(ServiceGroup, "SyslogIdentifier") {
 		service.Set(ServiceGroup, "SyslogIdentifier", "%N")
diff --git a/test/e2e/quadlet/basepodman.container b/test/e2e/quadlet/basepodman.container
index 88df7344a7..91e12ceb9c 100644
--- a/test/e2e/quadlet/basepodman.container
+++ b/test/e2e/quadlet/basepodman.container
@@ -1,4 +1,4 @@
-## assert-podman-final-args run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm -d --log-driver passthrough --runtime /usr/bin/crun --cgroups=split --sdnotify=conmon localhost/imagename
+## assert-podman-final-args run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm --log-driver passthrough --runtime /usr/bin/crun --cgroups=split --sdnotify=conmon  -d localhost/imagename
 
 [Container]
 Image=localhost/imagename
diff --git a/test/e2e/quadlet/oneshot.container b/test/e2e/quadlet/oneshot.container
new file mode 100644
index 0000000000..e61ea8efed
--- /dev/null
+++ b/test/e2e/quadlet/oneshot.container
@@ -0,0 +1,9 @@
+## !assert-podman-args "--sdnotify=conmon"
+## !assert-podman-args "--sdnotify=container"
+## assert-key-is Service Type oneshot
+
+[Container]
+Image=localhost/imagename
+
+[Service]
+Type=oneshot
diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go
index eed3afb97e..0129f2572f 100644
--- a/test/e2e/quadlet_test.go
+++ b/test/e2e/quadlet_test.go
@@ -366,6 +366,7 @@ var _ = Describe("quadlet system generator", func() {
 		Entry("network.quadlet.container", "network.quadlet.container"),
 		Entry("noimage.container", "noimage.container"),
 		Entry("notify.container", "notify.container"),
+		Entry("oneshot.container", "oneshot.container"),
 		Entry("other-sections.container", "other-sections.container"),
 		Entry("podmanargs.container", "podmanargs.container"),
 		Entry("ports.container", "ports.container"),
diff --git a/test/system/252-quadlet.bats b/test/system/252-quadlet.bats
index e583344acd..7ddebbc732 100644
--- a/test/system/252-quadlet.bats
+++ b/test/system/252-quadlet.bats
@@ -198,6 +198,29 @@ EOF
     service_cleanup $QUADLET_SERVICE_NAME failed
 }
 
+@test "quadlet - oneshot" {
+    local quadlet_file=$PODMAN_TMPDIR/oneshot_$(random_string).container
+    cat > $quadlet_file <<EOF
+[Container]
+Image=$IMAGE
+Exec=echo INITIALIZED
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+EOF
+
+    run_quadlet "$quadlet_file"
+
+    service_setup $QUADLET_SERVICE_NAME
+
+    # Ensure we have output. Output is synced by oneshot command exit
+    run journalctl "--since=$STARTED_TIME"  SYSLOG_IDENTIFIER="$QUADLET_SYSLOG_ID"
+    is "$output" '.*INITIALIZED.*'
+
+    service_cleanup $QUADLET_SERVICE_NAME inactive
+}
+
 @test "quadlet - volume" {
     local quadlet_file=$PODMAN_TMPDIR/basic_$(random_string).volume
     cat > $quadlet_file <<EOF