From e0ac53dba32f63c2e4e84965092e1b1ab18bb588 Mon Sep 17 00:00:00 2001 From: Timothy Stewart Date: Thu, 6 Aug 2026 09:08:11 -0500 Subject: [PATCH] fix(metallb): verify the metallb-system namespace actually exists - change the Test metallb-system namespace task to run k3s kubectl get namespace metallb-system instead of the bare -n metallb-system, which printed kubectl usage and always exited 0 - add a regression test that asserts the task uses an explicit get and fails if it ever regresses to the usage-only form - wire the new test into pre-commit --- .github/scripts/test-metallb-namespace.py | 72 +++++++++++++++++++++++ .pre-commit-config.yaml | 8 +++ roles/k3s_server_post/tasks/metallb.yml | 2 +- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/test-metallb-namespace.py 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