Repository: osbuild/osbuild
Pull request #2071 (2025-04-11 12:14:15)

Stages/waagent.conf: support additional options
Extend the stage to support setting new options:
 - Provisioning.UseCloudInit
 - Provisioning.Enabled

Extend the stage test to use them and add a simple stage unit test for
the schema.

Related to https://github.com/osbuild/images/issues/1416


Patch:
diff --git a/stages/org.osbuild.waagent.conf b/stages/org.osbuild.waagent.conf
index 69cadac8..424c7cee 100755
--- a/stages/org.osbuild.waagent.conf
+++ b/stages/org.osbuild.waagent.conf
@@ -12,10 +12,22 @@ def bool_to_y_n(b):
 
 
 def main(tree, options):
-    sshd_config = options.get("config", {})
-    resource_disk_format = sshd_config.get("ResourceDisk.Format")
-    resource_disk_enable_swap = sshd_config.get("ResourceDisk.EnableSwap")
+    waagent_config = options.get("config", {})
+    provisioning_use_cloud_init = waagent_config.get("Provisioning.UseCloudInit")
+    provisioning_enabled = waagent_config.get("Provisioning.Enabled")
+    resource_disk_format = waagent_config.get("ResourceDisk.Format")
+    resource_disk_enable_swap = waagent_config.get("ResourceDisk.EnableSwap")
     changes = {}
+    if provisioning_use_cloud_init is not None:
+        changes["provisioning.usecloudinit"] = {
+            "key": "Provisioning.UseCloudInit",
+            "value": bool_to_y_n(provisioning_use_cloud_init)
+        }
+    if provisioning_enabled is not None:
+        changes["provisioning.enabled"] = {
+            "key": "Provisioning.Enabled",
+            "value": bool_to_y_n(provisioning_enabled)
+        }
     if resource_disk_format is not None:
         changes["resourcedisk.format"] = {
             "key": "ResourceDisk.Format",
diff --git a/stages/org.osbuild.waagent.conf.meta.json b/stages/org.osbuild.waagent.conf.meta.json
index a7a46577..c2f813d4 100644
--- a/stages/org.osbuild.waagent.conf.meta.json
+++ b/stages/org.osbuild.waagent.conf.meta.json
@@ -16,6 +16,14 @@
         "description": "WALinuxAgent config options",
         "type": "object",
         "properties": {
+          "Provisioning.UseCloudInit": {
+            "description": "Enable or disable cloud-init provisioning.",
+            "type": "boolean"
+          },
+          "Provisioning.Enabled": {
+            "description": "Enable or disable provisioning.",
+            "type": "boolean"
+          },
           "ResourceDisk.Format": {
             "description": "Enable or disable disk formatting.",
             "type": "boolean"


Test patch:
diff --git a/stages/test/test_rhsm.py b/stages/test/test_rhsm.py
new file mode 100644
index 00000000..a91d2f75
--- /dev/null
+++ b/stages/test/test_rhsm.py
@@ -0,0 +1,143 @@
+#!/usr/bin/python3
+
+import re
+
+import pytest
+
+from osbuild.testutil import (
+    assert_jsonschema_error_contains,
+)
+
+STAGE_NAME = "org.osbuild.rhsm"
+
+
+@pytest.mark.parametrize("test_data,expected_err", [
+    # bad
+    ({
+        "dnf-plugins": {
+            "random": {
+                "enabled": False,
+            },
+            "subscription-manager": {
+                "enabled": False,
+            },
+        },
+    }, r"Additional properties are not allowed \('random' was unexpected\)"),
+    ({
+        "dnf-plugins": {
+            "product-id": {
+                "enhanced": False,
+            },
+            "subscription-manager": {
+                "enabled": False,
+            },
+        },
+    }, r"Additional properties are not allowed \('enhanced' was unexpected\)"),
+    ({
+        "subscription-manager": {
+            "rhsm": {
+                "manage_repos": 45,
+                "auto_enable_yum_plugins": False
+            },
+            "rhsmcertd": {
+                "auto_registration": False,
+            },
+        }
+    }, r"45 is not of type 'boolean'"),
+    ({
+        "subscription-manager": {
+            "rhsm": {
+                "random": False
+            },
+            "rhsmcertd": {
+                "auto_registration": False,
+            },
+        }
+    }, r"Additional properties are not allowed \('random' was unexpected\)"),
+    ({
+        "subscription-manager": {
+            "random": {},
+        }
+    }, r"Additional properties are not allowed \('random' was unexpected\)"),
+    ({
+        "subscription-manager": {
+            "rhsmcertd": {
+                "random": False,
+            },
+        }
+    }, r"Additional properties are not allowed \('random' was unexpected\)"),
+    # good
+    ({}, ""),
+    ({
+        "dnf-plugins": {},
+        "yum-plugins": {},
+        "subscription-manager": {},
+    }, ""),
+    ({
+        "dnf-plugins": {
+            "product-id": {
+                "enabled": False,
+            },
+            "subscription-manager": {
+                "enabled": False,
+            },
+        },
+        "yum-plugins": {
+            "product-id": {
+                "enabled": False,
+            },
+            "subscription-manager": {
+                "enabled": False,
+            },
+        },
+        "subscription-manager": {
+            "rhsm": {
+                "manage_repos": False,
+                "auto_enable_yum_plugins": False,
+            },
+            "rhsmcertd": {
+                "auto_registration": False,
+            },
+        },
+    }, ""),
+    ({
+        "dnf-plugins": {
+            "product-id": {
+                "enabled": True,
+            },
+            "subscription-manager": {
+                "enabled": True,
+            },
+        },
+        "yum-plugins": {
+            "product-id": {
+                "enabled": True,
+            },
+            "subscription-manager": {
+                "enabled": True,
+            },
+        },
+        "subscription-manager": {
+            "rhsm": {
+                "manage_repos": True,
+                "auto_enable_yum_plugins": True,
+            },
+            "rhsmcertd": {
+                "auto_registration": True,
+            },
+        },
+    }, ""),
+])
+def test_schema_validation(stage_schema, test_data, expected_err):
+    test_input = {
+        "type": STAGE_NAME,
+        "options": {},
+    }
+    test_input["options"].update(test_data)
+    res = stage_schema.validate(test_input)
+
+    if expected_err == "":
+        assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}"
+    else:
+        assert res.valid is False
+        assert_jsonschema_error_contains(res, re.compile(expected_err), expected_num_errs=1)
diff --git a/stages/test/test_waagent_conf.py b/stages/test/test_waagent_conf.py
new file mode 100644
index 00000000..9ab6cbdd
--- /dev/null
+++ b/stages/test/test_waagent_conf.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python3
+
+import re
+
+import pytest
+
+from osbuild.testutil import (
+    assert_jsonschema_error_contains,
+)
+
+STAGE_NAME = "org.osbuild.waagent.conf"
+
+
+@pytest.mark.parametrize("test_data,expected_err", [
+    # bad
+    ({}, r"'config' is a required property"),
+    ({"config": {"Unsupported": True}}, r"Additional properties are not allowed \('Unsupported' was unexpected\)"),
+    ({"config": {"Provisioning.UseCloudInit": 42}}, r"42 is not of type 'boolean'"),
+    ({"config": {"Provisioning.Enabled": "abc"}}, r"'abc' is not of type 'boolean'"),
+    ({"config": {"ResourceDisk.Format": 42}}, r"42 is not of type 'boolean'"),
+    ({"config": {"ResourceDisk.EnableSwap": "abc"}}, r"'abc' is not of type 'boolean'"),
+    # good
+    ({"config": {}}, ""),
+    ({
+        "config": {
+            "Provisioning.UseCloudInit": True,
+            "Provisioning.Enabled": True,
+            "ResourceDisk.Format": False,
+            "ResourceDisk.EnableSwap": False,
+        },
+    }, ""),
+])
+def test_schema_validation(stage_schema, test_data, expected_err):
+    test_input = {
+        "type": STAGE_NAME,
+        "options": {},
+    }
+    test_input["options"].update(test_data)
+    res = stage_schema.validate(test_input)
+
+    if expected_err == "":
+        assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}"
+    else:
+        assert res.valid is False
+        assert_jsonschema_error_contains(res, re.compile(expected_err), expected_num_errs=1)
diff --git a/test/data/stages/waagent.conf/b.json b/test/data/stages/waagent.conf/b.json
index 733354a0..cd54111a 100644
--- a/test/data/stages/waagent.conf/b.json
+++ b/test/data/stages/waagent.conf/b.json
@@ -681,6 +681,8 @@
           "type": "org.osbuild.waagent.conf",
           "options": {
             "config": {
+              "Provisioning.UseCloudInit": true,
+              "Provisioning.Enabled": false,
               "ResourceDisk.Format": false,
               "ResourceDisk.EnableSwap": true
             }
diff --git a/test/data/stages/waagent.conf/b.mpp.yaml b/test/data/stages/waagent.conf/b.mpp.yaml
index b0bb1566..f9498a89 100644
--- a/test/data/stages/waagent.conf/b.mpp.yaml
+++ b/test/data/stages/waagent.conf/b.mpp.yaml
@@ -30,6 +30,8 @@ pipelines:
       - type: org.osbuild.waagent.conf
         options:
           config:
+            Provisioning.UseCloudInit: true
+            Provisioning.Enabled: false
             ResourceDisk.Format: false
             ResourceDisk.EnableSwap: true
       - type: org.osbuild.waagent.conf
diff --git a/test/data/stages/waagent.conf/diff.json b/test/data/stages/waagent.conf/diff.json
index ea178f7b..d55b438c 100644
--- a/test/data/stages/waagent.conf/diff.json
+++ b/test/data/stages/waagent.conf/diff.json
@@ -5,7 +5,7 @@
     "/etc/waagent.conf": {
       "content": [
         "sha256:9908ed1eeb7f4cd29ef3f1baa4f87d1de6ad6adaa08a09d83861a4a1fb88fb3a",
-        "sha256:7551a6a82c9133811b3e1a22504250fe8ced1f3bd4b47b14fceed028316ef5f5"
+        "sha256:746a295fa3c6aa3d90295611a55401d6c4b0bff1d2bbc5fb45ec4112bee84354"
       ]
     }
   }