-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add supportedSplunkModinput output with python.version variants for Splunk 9.4 #213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,40 +18,69 @@ def has_features(features, section): | |
| return True | ||
|
|
||
|
|
||
| def _generate_supported_splunk(args, path): | ||
| _ALLOWED_SERVER_CONF_PYTHON_VERSIONS = {"python3", "force_python3"} | ||
|
|
||
|
|
||
| def _load_splunk_config(path): | ||
| if os.path.exists("splunk_matrix.conf"): | ||
| splunk_matrix = "splunk_matrix.conf" | ||
| else: | ||
| splunk_matrix = os.path.join(path, "splunk_matrix.conf") | ||
| config = configparser.ConfigParser() | ||
| config.read(splunk_matrix) | ||
| supported_splunk = [] | ||
| return config | ||
|
|
||
|
|
||
| def _iter_splunk_sections(args, config): | ||
| """Yield (section, props, base_entry) for each non-EOL, feature-matching Splunk version.""" | ||
| today = datetime.now().date() | ||
| for section in config.sections(): | ||
| if re.search(r"^\d+", section): | ||
| props = {} | ||
| supported_splunk_string = config[section]["SUPPORTED"] | ||
| eol = datetime.strptime(supported_splunk_string, "%Y-%m-%d").date() | ||
| today = datetime.now().date() | ||
| if today >= eol: | ||
| continue | ||
|
|
||
| if not has_features(args.features, config[section]): | ||
| continue | ||
| for k in config[section].keys(): | ||
| try: | ||
| value = config[section].getboolean(k) | ||
| except: | ||
| value = config[section][k] | ||
| props[k] = value | ||
| if not re.search(r"^\d+", section): | ||
| continue | ||
| eol = datetime.strptime(config[section]["SUPPORTED"], "%Y-%m-%d").date() | ||
| if today >= eol: | ||
| continue | ||
| if not has_features(args.features, config[section]): | ||
| continue | ||
| props = {} | ||
| for k in config[section].keys(): | ||
| try: | ||
| value = config[section].getboolean(k) | ||
| except ValueError: | ||
| value = config[section][k] | ||
| props[k] = value | ||
| base_entry = { | ||
| "version": props["version"], | ||
| "build": props["build"], | ||
| "islatest": (config["GENERAL"]["LATEST"] == section), | ||
| "isoldest": (config["GENERAL"]["OLDEST"] == section), | ||
| } | ||
| yield section, props, base_entry | ||
|
|
||
| supported_splunk.append( | ||
| { | ||
| "version": props["version"], | ||
| "build": props["build"], | ||
| "islatest": (config["GENERAL"]["LATEST"] == section), | ||
| "isoldest": (config["GENERAL"]["OLDEST"] == section), | ||
| } | ||
| ) | ||
|
|
||
| def _generate_supported_splunk(args, path): | ||
| config = _load_splunk_config(path) | ||
| return [base_entry for _, _, base_entry in _iter_splunk_sections(args, config)] | ||
|
|
||
|
|
||
| def _generate_supported_splunk_modinput(args, path): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [IMPORTANT] Maintainability + Tests — two things on this new function:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — factored the shared file-loading + EOL/feature-filtering loop into Unit tests added in
All 9 tests pass. |
||
| config = _load_splunk_config(path) | ||
| supported_splunk = [] | ||
| for _, props, base_entry in _iter_splunk_sections(args, config): | ||
| raw = props.get("server_conf_python_versions") | ||
| if raw: | ||
| for python_version in raw.split(","): | ||
| python_version = python_version.strip() | ||
| if python_version not in _ALLOWED_SERVER_CONF_PYTHON_VERSIONS: | ||
| raise ValueError( | ||
| f"Invalid server_conf_python_versions value: {python_version!r}. " | ||
| f"Allowed: {sorted(_ALLOWED_SERVER_CONF_PYTHON_VERSIONS)}" | ||
| ) | ||
| variant = dict(base_entry) | ||
| variant["serverConfPythonVersion"] = python_version | ||
| supported_splunk.append(variant) | ||
| else: | ||
| supported_splunk.append(base_entry) | ||
| return supported_splunk | ||
|
|
||
|
|
||
|
|
@@ -136,6 +165,15 @@ def main(): | |
| with open(os.environ["GITHUB_OUTPUT"], "a") as fh: | ||
| print(f"supportedSplunk={json.dumps(supported_splunk)}", file=fh) | ||
|
|
||
| supported_splunk_modinput = _generate_supported_splunk_modinput(args, path) | ||
| pprint.pprint( | ||
| f"Supported Splunk versions (modinput): {json.dumps(supported_splunk_modinput)}" | ||
| ) | ||
| with open(os.environ["GITHUB_OUTPUT"], "a") as fh: | ||
| print( | ||
| f"supportedSplunkModinput={json.dumps(supported_splunk_modinput)}", file=fh | ||
| ) | ||
|
|
||
| for splunk in supported_splunk: | ||
| if splunk["islatest"]: | ||
| pprint.pprint(f"Latest Splunk version: {json.dumps([splunk])}") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| import argparse | ||
| import configparser | ||
| import textwrap | ||
| from unittest.mock import patch | ||
|
|
||
| import pytest | ||
|
|
||
| from addonfactory_test_matrix_action.main import ( | ||
| _ALLOWED_SERVER_CONF_PYTHON_VERSIONS, | ||
| _generate_supported_splunk, | ||
| _generate_supported_splunk_modinput, | ||
| _load_splunk_config, | ||
| _iter_splunk_sections, | ||
| ) | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Helpers | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| _MATRIX_TEMPLATE = textwrap.dedent( | ||
| """\ | ||
| [GENERAL] | ||
| LATEST = 10.2 | ||
| OLDEST = 9.3 | ||
|
|
||
| [10.2] | ||
| VERSION = 10.2.2 | ||
| BUILD = aaaaaaaaaaaa | ||
| SUPPORTED = 2028-01-15 | ||
| PYTHON39 = true | ||
|
|
||
| [9.4] | ||
| VERSION = 9.4.10 | ||
| BUILD = bbbbbbbbbbbb | ||
| SUPPORTED = 2026-12-16 | ||
| PYTHON39 = true | ||
| SERVER_CONF_PYTHON_VERSIONS = python3,force_python3 | ||
|
|
||
| [9.3] | ||
| VERSION = 9.3.11 | ||
| BUILD = cccccccccccc | ||
| SUPPORTED = 2026-07-24 | ||
| PYTHON39 = true | ||
| """ | ||
| ) | ||
|
|
||
| # 9.3 is EOL relative to today (2026-07-24 < 2026-06-30 is false, but let's use | ||
| # a matrix where one section is genuinely EOL for that test) | ||
| _MATRIX_WITH_EOL = textwrap.dedent( | ||
| """\ | ||
| [GENERAL] | ||
| LATEST = 10.2 | ||
| OLDEST = 9.0 | ||
|
|
||
| [10.2] | ||
| VERSION = 10.2.2 | ||
| BUILD = aaaaaaaaaaaa | ||
| SUPPORTED = 2028-01-15 | ||
| PYTHON39 = true | ||
|
|
||
| [9.4] | ||
| VERSION = 9.4.10 | ||
| BUILD = bbbbbbbbbbbb | ||
| SUPPORTED = 2026-12-16 | ||
| PYTHON39 = true | ||
| SERVER_CONF_PYTHON_VERSIONS = python3,force_python3 | ||
|
|
||
| [9.0] | ||
| VERSION = 9.0.0 | ||
| BUILD = dddddddddddd | ||
| SUPPORTED = 2024-01-01 | ||
| PYTHON39 = false | ||
| """ | ||
| ) | ||
|
|
||
| _MATRIX_INVALID_VERSION = textwrap.dedent( | ||
| """\ | ||
| [GENERAL] | ||
| LATEST = 9.4 | ||
| OLDEST = 9.4 | ||
|
|
||
| [9.4] | ||
| VERSION = 9.4.10 | ||
| BUILD = bbbbbbbbbbbb | ||
| SUPPORTED = 2028-01-15 | ||
| SERVER_CONF_PYTHON_VERSIONS = invalid_value | ||
| """ | ||
| ) | ||
|
|
||
|
|
||
| def _args(features=None): | ||
| ns = argparse.Namespace() | ||
| ns.features = features | ||
| return ns | ||
|
|
||
|
|
||
| def _config_from_str(text): | ||
| cfg = configparser.ConfigParser() | ||
| cfg.read_string(text) | ||
| return cfg | ||
|
|
||
|
|
||
| def _mock_load(text): | ||
| """Return a patcher that replaces _load_splunk_config with one reading *text*.""" | ||
| cfg = _config_from_str(text) | ||
| return patch( | ||
| "addonfactory_test_matrix_action.main._load_splunk_config", | ||
| return_value=cfg, | ||
| ) | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # _generate_supported_splunk | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| class TestGenerateSupportedSplunk: | ||
| def test_returns_active_versions_only(self): | ||
| with _mock_load(_MATRIX_WITH_EOL): | ||
| result = _generate_supported_splunk(_args(), path="unused") | ||
| versions = [e["version"] for e in result] | ||
| assert "10.2.2" in versions | ||
| assert "9.4.10" in versions | ||
| assert "9.0.0" not in versions | ||
|
|
||
| def test_no_server_conf_python_version_field(self): | ||
| with _mock_load(_MATRIX_TEMPLATE): | ||
| result = _generate_supported_splunk(_args(), path="unused") | ||
| for entry in result: | ||
| assert "serverConfPythonVersion" not in entry | ||
|
|
||
| def test_islatest_isoldest_flags(self): | ||
| with _mock_load(_MATRIX_TEMPLATE): | ||
| result = _generate_supported_splunk(_args(), path="unused") | ||
| latest = [e for e in result if e["islatest"]] | ||
| oldest = [e for e in result if e["isoldest"]] | ||
| assert len(latest) == 1 and latest[0]["version"] == "10.2.2" | ||
| # 9.3 is the OLDEST in MATRIX_TEMPLATE (and still active as of today) | ||
| assert len(oldest) == 1 and oldest[0]["version"] == "9.3.11" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # _generate_supported_splunk_modinput | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| class TestGenerateSupportedSplunkModinput: | ||
| def test_94_expands_to_two_variants(self): | ||
| with _mock_load(_MATRIX_TEMPLATE): | ||
| result = _generate_supported_splunk_modinput(_args(), path="unused") | ||
| v94 = [e for e in result if e["version"] == "9.4.10"] | ||
| assert len(v94) == 2 | ||
| python_versions = {e["serverConfPythonVersion"] for e in v94} | ||
| assert python_versions == {"python3", "force_python3"} | ||
|
|
||
| def test_versions_without_setting_appear_once_without_field(self): | ||
| with _mock_load(_MATRIX_TEMPLATE): | ||
| result = _generate_supported_splunk_modinput(_args(), path="unused") | ||
| v102 = [e for e in result if e["version"] == "10.2.2"] | ||
| assert len(v102) == 1 | ||
| assert "serverConfPythonVersion" not in v102[0] | ||
|
|
||
| def test_total_entry_count(self): | ||
| # 10.2 → 1, 9.4 → 2, 9.3 → 1 = 4 (9.3 still active in MATRIX_TEMPLATE) | ||
| with _mock_load(_MATRIX_TEMPLATE): | ||
| result = _generate_supported_splunk_modinput(_args(), path="unused") | ||
| assert len(result) == 4 | ||
|
|
||
| def test_eol_version_excluded(self): | ||
| with _mock_load(_MATRIX_WITH_EOL): | ||
| result = _generate_supported_splunk_modinput(_args(), path="unused") | ||
| versions = [e["version"] for e in result] | ||
| assert "9.0.0" not in versions | ||
|
|
||
| def test_invalid_python_version_raises(self): | ||
| with _mock_load(_MATRIX_INVALID_VERSION): | ||
| with pytest.raises(ValueError, match="Invalid server_conf_python_versions"): | ||
| _generate_supported_splunk_modinput(_args(), path="unused") | ||
|
|
||
| def test_allowlist_contents(self): | ||
| assert _ALLOWED_SERVER_CONF_PYTHON_VERSIONS == {"python3", "force_python3"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[IMPORTANT] Reliability / Supply-chain — switching from a pinned, published image (
v3.2.0) toimage: 'Dockerfile'makes every consumer build from source on each run and loses image immutability/provenance. Understood that this is needed so the updated code is actually executed, but please follow up by publishing a new pinned tag (e.g.v3.3.0) and pointing the action back at it rather than leaving runtime builds permanently.