Files
k3s-ansible/.github/scripts/test-kube-vip-manifest.py
T
Timothy Stewart 332f36ceec chore(deps): bump k3s to v1.36.3 and kube-vip to v1.2.3
- Bump k3s_version to v1.36.3+k3s1 in sample group_vars
- Bump kube_vip_tag_version to v1.2.3 in group_vars, server role
  defaults and argument spec, and README variable table
- Update the kube-vip manifest regression test to assert v1.2.3
- Keep MetalLB at v0.16.0: v0.16.1 only exists as a chart-only
  metallb-chart-0.16.1 tag with no application or image release,
  so it must not be used as the controller or speaker image tag
2026-08-14 21:53:28 -05:00

163 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Render the kube-vip DaemonSet template and assert env key correctness.
kube-vip v1.2.3 reads `bgp_peers` and `vip_subnet`; it ignores the older
`bgppeers` and `vip_cidr` names. This test proves the rendered manifest uses
the keys the target image actually parses.
"""
from __future__ import print_function
import os
import subprocess
from jinja2 import Environment, FileSystemLoader, StrictUndefined
def repo_root():
return subprocess.check_output(
["git", "rev-parse", "--show-toplevel"], text=True
).strip()
def fail(message):
raise SystemExit("kube-vip manifest test failed: " + message)
def fake_ipsubnet(value):
# ansible.utils.ipsubnet -> network of the address as x.y.z.0/24
parts = value.split(".")
return ".".join(parts[:3]) + ".0/24"
def fake_ipaddr(_value, expr=None):
# ansible.utils.ipaddr('prefix') -> prefix length
return "24"
def fake_bool(value):
# Minimal stand-in for Ansible's truthiness filter used by the template.
if isinstance(value, bool):
return value
return str(value).lower() in ("1", "true", "yes", "on")
def fake_map(seq, *args, **kwargs):
# Minimal stand-in for Ansible's map() filter in the two forms used by the
# template: map(attribute='x') on a list of dicts, and map('join', sep) on
# a list of sequences.
if "attribute" in kwargs:
return [item[kwargs["attribute"]] for item in seq]
if kwargs:
# e.g. map(default='x') not used here; ignore unknown kwargs.
return list(seq)
if args:
filter_name = args[0]
sep = args[1] if len(args) > 1 else ""
if filter_name == "join":
return [sep.join(str(x) for x in item) for item in seq]
return list(seq)
def fake_zip(*seqs):
return list(zip(*seqs))
def render(env, extra_vars):
base_vars = {
"apiserver_endpoint": "192.168.30.222",
"kube_vip_iface": "",
"kube_vip_arp": True,
"kube_vip_bgp": True,
"kube_vip_bgp_routerid": "127.0.0.1",
"_kube_vip_bgp_peers": [
{"peer_address": "192.168.30.1", "peer_asn": "64512"},
{"peer_address": "192.168.30.2", "peer_asn": "64513"},
],
"kube_vip_tag_version": "v1.2.3",
}
base_vars.update(extra_vars)
template = env.get_template("vip.yaml.j2")
return template.render(**base_vars)
def main():
root = repo_root()
template_dir = os.path.join(root, "roles", "k3s_server", "templates")
env = Environment(
loader=FileSystemLoader(template_dir), undefined=StrictUndefined
)
env.filters["ansible.utils.ipsubnet"] = fake_ipsubnet
env.filters["ansible.utils.ipaddr"] = fake_ipaddr
env.filters["bool"] = fake_bool
env.filters["map"] = fake_map
env.filters["zip"] = fake_zip
# Multi-peer BGP armed: must emit bgp_peers, never bgppeers.
output = render(env, {})
if "name: bgp_peers" not in output:
fail("rendered manifest is missing bgp_peers")
if "name: bgppeers" in output:
fail("rendered manifest still uses the ignored bgppeers key")
if "name: vip_subnet" not in output:
fail("rendered manifest is missing vip_subnet")
if "name: vip_cidr" in output:
fail("rendered manifest still uses the ignored vip_cidr key")
if "192.168.30.1:64512,192.168.30.2:64513" not in output:
fail("bgp_peers value is not comma-separated address:ASN entries")
if "ghcr.io/kube-vip/kube-vip:v1.2.3" not in output:
fail("kube-vip image tag is not v1.2.3")
# BGP enabled with no merged peers: single-peer fallback vars, no bgp_peers.
output = render(
env,
{
"_kube_vip_bgp_peers": [],
"kube_vip_bgp_as": "64513",
"kube_vip_bgp_peeraddress": "192.168.30.1",
"kube_vip_bgp_peeras": "64512",
},
)
if "name: bgp_as" not in output:
fail("single-peer bgp_as was not rendered")
if "name: bgp_peers" in output:
fail("bgp_peers present even though the peer list is empty")
# kube_vip_endpoint defaults to null (defined in role defaults): the
# address and subnet must fall back to the apiserver endpoint. default()
# without a truthy flag does NOT fall back on null, only on undefined, so
# this case pins the null runtime condition to prevent that regression.
output = render(
env,
{
"_kube_vip_bgp_peers": [],
"kube_vip_endpoint": None,
"kube_vip_arp": True,
"kube_vip_bgp": False,
},
)
if "value: 192.168.30.222" not in output:
fail("null kube_vip_endpoint does not fall back to apiserver_endpoint")
# kube_vip_endpoint set: overrides the internal listening address AND the
# subnet derivation while the advertised apiserver_endpoint stays separate.
output = render(
env,
{
"_kube_vip_bgp_peers": [],
"kube_vip_endpoint": "10.66.1.5",
"kube_vip_arp": True,
"kube_vip_bgp": False,
},
)
if "value: 10.66.1.5" not in output:
fail("kube_vip_endpoint did not override the address")
if "value: 192.168.30.222" in output:
fail("apiserver_endpoint leaked into address when kube_vip_endpoint set")
print("kube-vip manifest regression test passed")
if __name__ == "__main__":
main()