Repository: osbuild/osbuild
Pull request #2030 (2025-02-25 21:29:48)

stages: add insights-client config stage (HMS-5568)
Help and feedback much appreciated, it is entirely possible I missed something :) 

The goal is to create a stage that configures the insights-client proxy url

/jira-epic hms-5317

JIRA: [HMS-5568](https://issues.redhat.com/browse/HMS-5568)

rverdile: /jira-epic hms-5317
schuellerf: /jira-epic hms-5317
schuellerf: @rverdile just as an info - I "retried" the `jira-epic` command because of the lowercase "bug" I just fixed…
https://github.com/osbuild/pr-best-practices/pull/20
rverdile: /jira-epic HMS-5317
rverdile: @schuellerf thanks, it seemed to need the uppercase anyway
rverdile: @achilleas-k @supakeen thanks for the reviews! I improved the test case by adding more checks and removed the dependency on fileinput, replacing with the `open()` function
supakeen: > @achilleas-k @supakeen thanks for the reviews! I improved the test case by adding more checks and removed the dependency on fileinput, replacing with the `open()` function

Thanks, this is much more straightforward now, I had one small comment about the test case.

Patch:
diff --git a/stages/org.osbuild.insights-client.config b/stages/org.osbuild.insights-client.config
new file mode 100755
index 00000000..a06cbf6f
--- /dev/null
+++ b/stages/org.osbuild.insights-client.config
@@ -0,0 +1,50 @@
+#!/usr/bin/python3
+
+import os
+import sys
+
+import osbuild.api
+
+
+def main(tree, options):
+    insights_client_config = options.get("config", {})
+    proxy = insights_client_config.get("proxy")
+    path = options.get("path", "/etc/insights-client/insights-client.conf")
+    path = os.path.join(tree, path.lstrip("/"))
+
+    changes = {}
+    if proxy is not None:
+        changes["proxy"] = {"key": "proxy", "value": proxy}
+
+    if not os.path.exists(path):
+        with open(path, 'w', encoding="utf8"):
+            pass
+
+    # For each of the configured options, find the first non-commented out instance
+    # of the option and replace it (if necessary). If it does not already exist, append
+    # the option to the end of the file.
+    # Keys are case insensitive, values are not. Try to preserve the key and default to
+    # camel-case.
+    with open(path, 'r', encoding='utf8') as f:
+        lines = f.readlines()
+
+    with open(path, 'w', encoding='utf8') as f:
+        for line in lines:
+            line_list = line.split('=')
+            if len(line_list) == 2:
+                key, current_value = line_list
+                entry = changes.pop(key, None)
+                if entry is not None and current_value != entry['value']:
+                    f.write(f"{key}={entry['value']}\n")
+                    continue
+            f.write(line)
+        for entry in changes.values():
+            f.write(f"{entry['key']}={entry['value']}\n")
+
+    return 0
+
+
+if __name__ == '__main__':
+    args = osbuild.api.arguments()
+    r = main(args["tree"], args["options"])
+    sys.exit(r)
diff --git a/stages/org.osbuild.insights-client.config.meta.json b/stages/org.osbuild.insights-client.config.meta.json
new file mode 100644
index 00000000..a1db27df
--- /dev/null
+++ b/stages/org.osbuild.insights-client.config.meta.json
@@ -0,0 +1,31 @@
+{
+  "summary": "Configure insights-client.",
+  "description": [
+    "Configures insights-client.",
+    "Creates a new config file if one does not exist,",
+    "or modifies the existing one."
+  ],
+  "schema": {
+    "additionalProperties": false,
+    "required": [
+      "config"
+    ],
+    "properties": {
+      "config": {
+        "additionalProperties": false,
+        "description": "insights-client config options",
+        "type": "object",
+        "properties": {
+          "proxy": {
+            "description": "URL for the proxy.",
+            "type": "string"
+          }
+        }
+      },
+      "path": {
+        "description": "path to the insights-client config file",
+        "type": "string"
+      }
+    }
+  }
+}


Test patch:
diff --git a/stages/test/test_insights_client_conf.py b/stages/test/test_insights_client_conf.py
new file mode 100644
index 00000000..69f56e36
--- /dev/null
+++ b/stages/test/test_insights_client_conf.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python3
+
+STAGE_NAME = "org.osbuild.insights-client.config"
+
+CONF_FILE = """[insights-client]
+# Example options in this file are the defaults
+
+# Change log level, valid options DEBUG, INFO, WARNING, ERROR, CRITICAL. Default DEBUG
+#loglevel=DEBUG
+
+# Attempt to auto configure with Satellite server
+#auto_config=True
+
+# URL for your proxy.  Example: http://user:pass@192.168.100.50:8080
+#proxy=
+proxy=https://localhost/
+"""
+
+
+def test_insights_client_conf(tmp_path, stage_module):
+    tree = tmp_path / "tree"
+    path = tree / "etc/insights-client/"
+    path.mkdir(parents=True)
+
+    options = {
+        "config": {
+            "proxy": "test-proxy"
+        }
+    }
+    stage_module.main(tree, options)
+
+    with open(path / "insights-client.conf", 'r', encoding="utf8") as f:
+        proxy = f.readline()
+        expected_proxy = options["config"]["proxy"]
+        expected = f"proxy={expected_proxy}\n"
+        assert proxy == expected
+
+
+def test_insights_client_conf_already_exists(tmp_path, stage_module):
+    tree = tmp_path / "tree"
+    path = tree / "etc/insights-client/"
+    path.mkdir(parents=True)
+
+    with open(path / "insights-client.conf", "w", encoding="utf8") as f:
+        f.write(CONF_FILE)
+
+    options = {
+        "config": {
+            "proxy": "test-proxy"
+        }
+    }
+    stage_module.main(tree, options)
+
+    with open(path / "insights-client.conf", 'r', encoding="utf8") as f:
+        expected_proxy = options["config"]["proxy"]
+        expected = f"proxy={expected_proxy}\n"
+        original_proxy = "proxy=https://localhost/"
+        commented_proxy = f"#proxy={expected_proxy}\n"
+        lines = f.readlines()
+        assert expected in lines
+        assert original_proxy not in lines
+        assert commented_proxy not in lines