Collection of cookbooks for Podman Quadlets
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

143 lines
6.5 KiB

"""Fresh-install tests for the NetBird control plane.
The test ignition (fcos-test.ign) contains the cookbook and its dependencies but NOT the
examples or the dependency hooks (both are excluded from the production package). So this
module injects, via ``fcos_extra_files``:
* the PostgreSQL server config (config.env) — otherwise postgresql never starts and
management loops waiting for the database,
* the operator-provided NetBird config (read from this cookbook's own config/examples/),
* the PostgreSQL init hook that creates the netbird database (other/postgresql/netbird.sql),
* the Traefik routing hook (other/traefik/netbird.yaml) and a minimal Traefik main config.
The tests are self-sufficient: they assert what holds without a working OIDC provider (units
active, ports listening, generated config in place, database reachable, Traefik routing to the
right backend). They do not attempt a login flow.
"""
import subprocess
import textwrap
from pathlib import Path
import pytest
from helpers import TestNetbird
COOKBOOK_DIR = Path(__file__).resolve().parent.parent
# PostgreSQL server config for the dependency. config.env ships only as an example (excluded
# from the test ignition), so it must be injected or postgresql.target never comes up.
POSTGRESQL_CONFIG_ENV = textwrap.dedent("""\
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=postgres
POSTGRES_HOST_AUTH_METHOD=scram-sha-256
POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
POSTGRES_ARGS=-h 127.0.0.1
PGPORT=5432
PG_MAJOR=18
POSTGRES_BACKUP_RETENTION=7
""")
# Minimal Traefik main configuration: a file provider watching conf.d and the two entry
# points. TLS is served self-signed (the netbird.yaml routers use bare `tls: {}`), so no ACME
# / certificate resolver is needed for the tests.
TRAEFIK_YAML = """\
api:
dashboard: false
# The traefik cookbook ships a packaged conf.d/ping.yaml that references ping@internal;
# enable manualRouting so that router loads cleanly.
ping:
manualRouting: true
log:
level: INFO
accesslog: false
global:
sendanonymoususage: false
checknewversion: false
entryPoints:
http:
address: ":80"
https:
address: ":443"
providers:
file:
directory: /etc/traefik/conf.d/
watch: true
"""
def _read(rel: str) -> str:
return (COOKBOOK_DIR / rel).read_text()
@pytest.fixture(scope="package")
def fcos_vm_config() -> tuple[int, int, int, int]:
"""More resources: five NetBird images plus PostgreSQL and Traefik to pull and start."""
return (6144, 4, 50, 100)
@pytest.fixture(scope="module")
def fcos_extra_files() -> dict:
"""Inject operator config + dependency config/hooks that the test ignition does not carry."""
return {
# PostgreSQL server config (dependency): without it postgresql.target never starts.
"/etc/quadlets/postgresql/config.env": (POSTGRESQL_CONFIG_ENV, 0, 0, 0o600),
# Traefik main config + the routing hook shipped by this cookbook.
"/etc/quadlets/traefik/traefik.yaml": (TRAEFIK_YAML, 10001, 10000, 0o644),
"/etc/quadlets/traefik/conf.d/netbird.yaml": (_read("other/traefik/netbird.yaml"), 10001, 10000, 0o644),
# PostgreSQL init hook: creates the netbird database/user on first boot.
"/etc/quadlets/postgresql/init.d/netbird.sql": (_read("other/postgresql/netbird.sql"), 10004, 10000, 0o600),
# Operator-provided NetBird config (working-but-insecure examples).
"/etc/quadlets/netbird/management.json": (_read("config/examples/management.json"), 10035, 10000, 0o640),
"/etc/quadlets/netbird/management.env": (_read("config/examples/management.env"), 0, 0, 0o600),
"/etc/quadlets/netbird/dashboard.env": (_read("config/examples/dashboard.env"), 0, 0, 0o600),
"/etc/quadlets/netbird/relay.env": (_read("config/examples/relay.env"), 0, 0, 0o600),
"/etc/quadlets/netbird/turnserver.conf": (_read("config/examples/turnserver.conf"), 10035, 10000, 0o640),
}
class TestNetbirdInstall(TestNetbird):
"""Verify the NetBird control plane comes up cleanly on a fresh VM boot."""
def test_management_connected_to_postgres(self, fcos_host):
"""Management must have selected the Postgres store engine and not be crash-looping."""
result = fcos_host.run("podman logs netbird-management 2>&1 | grep -i 'Postgres store engine'")
assert result.rc == 0, "management did not report using the Postgres store engine"
restarts = fcos_host.run("systemctl show -p NRestarts --value netbird-management.service")
assert restarts.stdout.strip() == "0", f"management restarted {restarts.stdout.strip()} times"
def test_netbird_database_exists(self, fcos_host):
"""The postgresql hook must have created the netbird database."""
result = fcos_host.run(
"podman exec postgresql-server psql -U postgres -tAc "
"\"SELECT 1 FROM pg_database WHERE datname='netbird'\""
)
assert result.stdout.strip() == "1", f"netbird database missing: {result.stdout} {result.stderr}"
def test_dashboard_served_through_traefik(self, fcos_vm):
"""Traefik must serve the dashboard SPA over HTTPS (self-signed) on the netbird host."""
result = subprocess.run(
["curl", "-sS", "-k", "-o", "/dev/null", "-w", "%{http_code}",
"--resolve", f"netbird:443:{fcos_vm.ip}", "https://netbird/"],
check=False, capture_output=True, text=True,
)
assert result.stdout.strip() == "200", f"dashboard not served: got {result.stdout!r} {result.stderr}"
def test_management_api_reachable_through_traefik(self, fcos_vm):
"""Traefik must route /api to management (401 = reached and demands auth, not 502)."""
result = subprocess.run(
["curl", "-sS", "-k", "-o", "/dev/null", "-w", "%{http_code}",
"--resolve", f"netbird:443:{fcos_vm.ip}", "https://netbird/api/users"],
check=False, capture_output=True, text=True,
)
assert result.stdout.strip() == "401", f"/api not routed to management: got {result.stdout!r}"
def test_relay_reachable_through_traefik(self, fcos_vm):
"""Traefik must route /relay to the relay (426 = WebSocket upgrade required, not 502)."""
result = subprocess.run(
["curl", "-sS", "-k", "-o", "/dev/null", "-w", "%{http_code}",
"--resolve", f"netbird:443:{fcos_vm.ip}", "https://netbird/relay"],
check=False, capture_output=True, text=True,
)
assert result.stdout.strip() == "426", f"/relay not routed to relay: got {result.stdout!r}"