Repository: osbuild/osbuild
Pull request #2067 (2025-04-07 18:06:28)

stages/systemd.unit.create: support StandardOutput (RHEL-79065)
Add support for the StandardOutput option in the [Service] section of
systemd units.

https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#StandardOutput=



Patch:
diff --git a/stages/org.osbuild.systemd.unit.create.meta.json b/stages/org.osbuild.systemd.unit.create.meta.json
index 5acc979f..705e43ea 100644
--- a/stages/org.osbuild.systemd.unit.create.meta.json
+++ b/stages/org.osbuild.systemd.unit.create.meta.json
@@ -28,6 +28,7 @@
     "    - 'ExecStart' - [string]",
     "    - 'Environment' - [object]",
     "    - 'EnvironmentFile' - [string]",
+    "    - 'StandardOutput' - [string]",
     "  - 'Mount' section",
     "    - 'What' - string",
     "    - 'Where' - string",
@@ -301,6 +302,10 @@
                 "items": {
                   "type": "string"
                 }
+              },
+              "StandardOutput": {
+                "type": "string",
+                "pattern": "^(inherit|null|tty|journal|kmsg|journal\\+console|kmsg\\+console|file:\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+|append:\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+|truncate:\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+|socket|fd:.+)$"
               }
             }
           },


Test patch:
diff --git a/stages/test/test_systemd_unit_create.py b/stages/test/test_systemd_unit_create.py
index d4f9de42..582b7450 100644
--- a/stages/test/test_systemd_unit_create.py
+++ b/stages/test/test_systemd_unit_create.py
@@ -53,6 +53,188 @@ STAGE_NAME = "org.osbuild.systemd.unit.create"
         },
         "",
     ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "inherit",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "null",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "tty",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "journal",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "kmsg",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "journal+console",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "kmsg+console",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "file:/var/log/example.log",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "append:/var/log/app.log",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "append:/root/important.txt",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "truncate:/debug.log",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "socket",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "fd:stdout",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "fd:whatever",
+                },
+                "Install": {},
+            },
+        },
+        "",
+    ),
 
     # bad
     # # No filename
@@ -91,6 +273,34 @@ STAGE_NAME = "org.osbuild.systemd.unit.create"
     ({"filename": "foo.swap", "config": {"Unit": {}, "Service": {}, "Swap": {"What": ""}, "Install": {}}},
      "{'Unit': {}, 'Service': {}, 'Swap': {'What': ''}, "
      "'Install': {}} is not valid under any of the given schemas"),
+
+    # # bad StandardOutput values
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "syslog",
+                },
+                "Install": {},
+            },
+        },
+        "'syslog' does not match",
+    ),
+    (
+        {
+            "filename": "stdout-test.service",
+            "config": {
+                "Unit": {},
+                "Service": {
+                    "StandardOutput": "file:",
+                },
+                "Install": {},
+            },
+        },
+        "'file:' does not match",
+    ),
 ])
 @pytest.mark.parametrize("stage_schema", ["1"], indirect=True)
 def test_schema_validation(stage_schema, test_data, expected_err):
@@ -225,7 +435,8 @@ def test_systemd_unit_create(tmp_path, stage_module, unit_type, unit_path, expec
                 "EnvironmentFile": [
                     "/etc/example.env",
                     "/etc/second.env",
-                ]
+                ],
+                "StandardOutput": "append:/var/log/mkdir.log",
             },
             "Install": {
                 "WantedBy": [
@@ -289,6 +500,7 @@ def test_systemd_unit_create(tmp_path, stage_module, unit_type, unit_path, expec
     Environment="TRACE=1"
     EnvironmentFile=/etc/example.env
     EnvironmentFile=/etc/second.env
+    StandardOutput=append:/var/log/mkdir.log
 
     [Install]
     WantedBy=local-fs.target
diff --git a/test/data/stages/systemd.unit.create/b.json b/test/data/stages/systemd.unit.create/b.json
index cc9f36b0..18705f2f 100644
--- a/test/data/stages/systemd.unit.create/b.json
+++ b/test/data/stages/systemd.unit.create/b.json
@@ -698,7 +698,8 @@
                 ],
                 "EnvironmentFile": [
                   "/etc/example.env"
-                ]
+                ],
+                "StandardOutput": "journal+console"
               },
               "Install": {
                 "WantedBy": [
diff --git a/test/data/stages/systemd.unit.create/b.mpp.yaml b/test/data/stages/systemd.unit.create/b.mpp.yaml
index 5aa896f8..36665d7a 100644
--- a/test/data/stages/systemd.unit.create/b.mpp.yaml
+++ b/test/data/stages/systemd.unit.create/b.mpp.yaml
@@ -54,6 +54,7 @@ pipelines:
                   value: "1"
               EnvironmentFile:
                 - "/etc/example.env"
+              StandardOutput: "journal+console"
             Install:
               WantedBy:
                 - local-fs.target
diff --git a/test/data/stages/systemd.unit.create/diff.json b/test/data/stages/systemd.unit.create/diff.json
index dd936245..7662a1a5 100644
--- a/test/data/stages/systemd.unit.create/diff.json
+++ b/test/data/stages/systemd.unit.create/diff.json
@@ -11,7 +11,7 @@
     "/etc/systemd/user/create-directory.service": {
       "content": [
         "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
-        "sha256:69aa663d58cc063ea2d7659ac06354335c26e69e8fdb0036f952b082a70b5642"
+        "sha256:81ee5966cdfedee4d43a304e19a4c3655eae4c215f1856f245d137a4982d3601"
       ]
     },
     "/usr/lib/systemd/system/dev-disk-by-uuid-4f581be3-e8c2-4b44-9456-54df3ab92944.swap": {