diff --git a/.github/scripts/test-metallb-namespace.py b/.github/scripts/test-metallb-namespace.py new file mode 100644 index 0000000..4e07cc4 --- /dev/null +++ b/.github/scripts/test-metallb-namespace.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Regression test for the MetalLB namespace existence check. + +The "Test metallb-system namespace" task in +roles/k3s_server_post/tasks/metallb.yml must actually verify the namespace +exists. A previous version ran `k3s kubectl -n metallb-system` with no +subcommand, which only printed a usage page and always exited 0, so the task +always succeeded even when the namespace did not exist (issue #350). + +This test loads the real task and asserts the command performs an explicit +`get namespace metallb-system`, which returns non-zero when the namespace is +absent. +""" + +from __future__ import print_function + +import os +import subprocess + +import yaml + + +def repo_root(): + return subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], text=True + ).strip() + + +def fail(message): + raise SystemExit( + "MetalLB namespace test failed: " + message + ) + + +def main(): + task_file = os.path.join( + repo_root(), "roles", "k3s_server_post", "tasks", "metallb.yml" + ) + with open(task_file, encoding="utf-8") as handle: + tasks = yaml.safe_load(handle) + + task = None + for entry in tasks: + if entry.get("name") == "Test metallb-system namespace": + task = entry + break + if task is None: + fail("could not find the 'Test metallb-system namespace' task") + + cmd = task.get("ansible.builtin.command") + if not cmd: + cmd = task.get("command") + if not cmd: + fail("task does not use ansible.builtin.command") + + command_text = cmd if isinstance(cmd, str) else " ".join(cmd) + + # A bare `-n metallb-system` with no subcommand prints kubectl usage and + # always exits 0, so it never proves the namespace exists. The fix must + # use an explicit get. + if "get namespace metallb-system" not in command_text: + fail( + "command does not run 'get namespace metallb-system'; " + "the task would only print usage and never verify the namespace " + "(got: {0!r})".format(command_text) + ) + + print("MetalLB namespace check regression test passed") + + +if __name__ == "__main__": + main() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4858b55..9f7421b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -113,6 +113,14 @@ repos: - Jinja2>=3.1 pass_filenames: false files: ^roles/k3s_server_post/templates/metallb\.crs\.j2$|^\.github/scripts/test-metallb-interfaces\.py$ + - id: metallb-namespace-test + name: MetalLB namespace test + entry: python3 .github/scripts/test-metallb-namespace.py + language: python + additional_dependencies: + - PyYAML + pass_filenames: false + files: ^roles/k3s_server_post/tasks/metallb\.yml$|^\.github/scripts/test-metallb-namespace\.py$ - id: metallb-deploy-condition-test name: MetalLB deploy condition test entry: python3 .github/scripts/test-metallb-deploy-condition.py diff --git a/roles/k3s_server_post/tasks/metallb.yml b/roles/k3s_server_post/tasks/metallb.yml index 5e814c1..fb4d779 100644 --- a/roles/k3s_server_post/tasks/metallb.yml +++ b/roles/k3s_server_post/tasks/metallb.yml @@ -40,7 +40,7 @@ - name: Test metallb-system namespace ansible.builtin.command: >- - {{ k3s_kubectl_binary | default('k3s kubectl') }} -n metallb-system + {{ k3s_kubectl_binary | default('k3s kubectl') }} get namespace metallb-system changed_when: false with_items: "{{ groups[group_name_master | default('master')] }}" run_once: true