feat(mumble F2-14c): drop cc-ci compose.host-ports.yml fork; deploy 0.2.0 base minimally, add native host-ports on upgrade-to-latest via new UPGRADE_EXTRA_ENV harness hook + COMPOSE_FILE-aware READY_PROBE/install skip
This commit is contained in:
@ -81,8 +81,8 @@ def recipe_checkout(recipe: str, version: str) -> None:
|
||||
|
||||
path = os.path.expanduser(f"~/.abra/recipes/{recipe}")
|
||||
# -f (force): the version-pinning checkout must yield the EXACT ref tree. Without it, a cc-ci
|
||||
# install_steps-provided overlay (e.g. mumble's compose.host-ports.yml, copied into a version that
|
||||
# predates it) is an UNTRACKED file that collides with the same path TRACKED in a later ref, and
|
||||
# install_steps-provided overlay (e.g. discourse's compose.ccci.yml, copied into the pinned base)
|
||||
# is an UNTRACKED file that collides with the same path TRACKED in a later ref, and
|
||||
# `git checkout <ref>` aborts ("untracked working tree files would be overwritten"). Force resolves
|
||||
# it by writing the ref's tracked version. Safe: we never want local recipe-tree state preserved
|
||||
# across a version switch (and chaos deploys re-provide the overlay via install_steps when needed).
|
||||
@ -137,6 +137,25 @@ def env_set(domain: str, key: str, value: str) -> None:
|
||||
fh.write("\n".join(out) + "\n")
|
||||
|
||||
|
||||
def env_get(domain: str, key: str) -> str | None:
|
||||
"""Read a key from the app's .env (last uncommented assignment wins). None if absent. Symmetric
|
||||
with env_set; abra has no getter. Strips surrounding quotes from the value."""
|
||||
import os
|
||||
import re
|
||||
|
||||
path = os.path.expanduser(f"~/.abra/servers/default/{domain}.env")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
pat = re.compile(rf"^\s*{re.escape(key)}=(.*)$")
|
||||
val = None
|
||||
with open(path) as fh:
|
||||
for ln in fh.read().splitlines():
|
||||
m = pat.match(ln)
|
||||
if m:
|
||||
val = m.group(1).strip().strip('"').strip("'")
|
||||
return val
|
||||
|
||||
|
||||
def secret_generate(domain: str, timeout: int = 300) -> None:
|
||||
# -m avoids the TTY/table (ioctl) path; output (which contains the generated values) is
|
||||
# captured by _run and never logged. -C -o keep the recipe at the PR checkout (without -o it
|
||||
|
||||
@ -18,7 +18,7 @@ import socket
|
||||
import ssl
|
||||
import time
|
||||
|
||||
from . import lifecycle
|
||||
from . import abra, lifecycle
|
||||
|
||||
# A recipe is backup-capable iff a compose file carries a truthy backupbot.backup label.
|
||||
_BACKUPBOT_RE = re.compile(r"backupbot\.backup\b[^\n]*\btrue\b", re.IGNORECASE)
|
||||
@ -244,6 +244,17 @@ def perform_upgrade(
|
||||
before = lifecycle.deployed_identity(domain)
|
||||
if head_ref:
|
||||
lifecycle.recipe_checkout_ref(recipe, head_ref)
|
||||
# UPGRADE_EXTRA_ENV (F2-14c): a recipe may need different app .env for the upgrade-TARGET deploy
|
||||
# than for the base — e.g. mumble's `compose.host-ports.yml` overlay exists ONLY in the newer
|
||||
# (target) version, so the base deploys minimally WITHOUT it and the upgrade adds it to COMPOSE_FILE
|
||||
# here, after the PR-head checkout (which ships the overlay) and before the chaos redeploy that
|
||||
# picks up the new .env. Dict or callable(domain)->dict. No-op for recipes without it.
|
||||
upgrade_env = meta.get("UPGRADE_EXTRA_ENV") or {}
|
||||
if callable(upgrade_env):
|
||||
upgrade_env = upgrade_env(domain) or {}
|
||||
for k, v in upgrade_env.items():
|
||||
print(f" upgrade-env: {k}={v}", flush=True)
|
||||
abra.env_set(domain, k, v)
|
||||
# HQ1: warm the NEW-version image set before the chaos redeploy (the head_ref checkout's pinned
|
||||
# tags) so a pull failure is a clear pre-deploy error and convergence isn't pull-bound.
|
||||
lifecycle.prepull_images(recipe, domain)
|
||||
|
||||
@ -231,10 +231,10 @@ def deploy_app(
|
||||
flush=True,
|
||||
)
|
||||
chaos = True
|
||||
# A recipe may force a chaos base deploy via recipe_meta CHAOS_BASE_DEPLOY=True when cc-ci adds
|
||||
# an untracked compose overlay to the recipe checkout (e.g. mumble's host-ports.yml, provided
|
||||
# by install_steps for older versions that predate it). The untracked file makes abra's
|
||||
# pinned-deploy clean-tree check FATA ('has locally unstaged changes'); chaos skips lint +
|
||||
# A recipe may force a chaos base deploy via recipe_meta CHAOS_BASE_DEPLOY=True when an
|
||||
# install_steps hook adds an untracked compose overlay to the recipe checkout (e.g. discourse's
|
||||
# compose.ccci.yml, provided by install_steps for the pinned base). The untracked file makes
|
||||
# abra's pinned-deploy clean-tree check FATA ('has locally unstaged changes'); chaos skips lint +
|
||||
# the clean-tree gate and deploys the EXPLICITLY-checked-out pinned version (we already ran
|
||||
# recipe_checkout(version) above) — NOT latest. Same mechanism as the lightweight-tag branch.
|
||||
elif _recipe_meta_flag(recipe, "CHAOS_BASE_DEPLOY"):
|
||||
|
||||
@ -194,7 +194,7 @@ def _load_meta(recipe: str) -> dict:
|
||||
ns: dict = {}
|
||||
with open(path) as fh:
|
||||
exec(compile(fh.read(), path, "exec"), ns) # noqa: S102 (trusted, in-repo)
|
||||
for k in list(meta) + ["BACKUP_CAPABLE", "SKIP_GENERIC", "OIDC_AT_INSTALL", "READY_PROBE", "UPGRADE_BASE_VERSION", "BACKUP_VERIFY"]:
|
||||
for k in list(meta) + ["BACKUP_CAPABLE", "SKIP_GENERIC", "OIDC_AT_INSTALL", "READY_PROBE", "UPGRADE_BASE_VERSION", "BACKUP_VERIFY", "UPGRADE_EXTRA_ENV"]:
|
||||
if k in ns:
|
||||
meta[k] = ns[k]
|
||||
return meta
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
---
|
||||
# cc-ci-owned copy of the upstream mumble `compose.host-ports.yml` overlay (identical content).
|
||||
# Provided to the recipe checkout by tests/mumble/install_steps.sh so that 64738 is published on the
|
||||
# cc-ci host for EVERY version under test — the upstream overlay only exists from recipe version
|
||||
# 1.0.0+, but the upgrade tier's base deploy is the previous published version (0.2.0+), which
|
||||
# predates it. On-host tests (cc-ci-run) reach the voice server at 127.0.0.1:64738 via this publish.
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
ports:
|
||||
- target: 64738
|
||||
published: 64738
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 64738
|
||||
published: 64738
|
||||
protocol: udp
|
||||
mode: host
|
||||
@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# mumble — INSTALL-TIME hook (Phase 2 Q4.2). Runs during the install tier AFTER `abra app new` +
|
||||
# EXTRA_ENV + `abra app secret generate` and BEFORE the single `abra app deploy`
|
||||
# (lifecycle.py::_run_install_steps), with CCCI_RECIPE / CCCI_APP_DOMAIN / CCCI_APP_ENV in env.
|
||||
#
|
||||
# Purpose: guarantee `compose.host-ports.yml` exists in the recipe checkout for EVERY version under
|
||||
# test. mumble's voice server speaks a non-HTTP TLS protocol on 64738; cc-ci's tests run on-host
|
||||
# (cc-ci-run) and reach it at 127.0.0.1:64738 via a host-published port. The upstream recipe ships
|
||||
# compose.host-ports.yml only from version 1.0.0+, but the upgrade tier's base deploy is the previous
|
||||
# published version (0.2.0+), which predates it — so EXTRA_ENV's COMPOSE_FILE (which references the
|
||||
# overlay) would fail to resolve on that base deploy. We provide an identical overlay here so the
|
||||
# overlay is present whether the checked-out version ships it natively (no-op) or not (copied).
|
||||
set -euo pipefail
|
||||
|
||||
: "${CCCI_RECIPE:?missing CCCI_RECIPE}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
RECIPE_DIR="${HOME}/.abra/recipes/${CCCI_RECIPE}"
|
||||
|
||||
if [ ! -d "$RECIPE_DIR" ]; then
|
||||
echo " mumble install_steps: recipe dir $RECIPE_DIR missing — cannot provide host-ports overlay" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$RECIPE_DIR/compose.host-ports.yml" ]; then
|
||||
echo " mumble install_steps: compose.host-ports.yml already present (native to this version)"
|
||||
else
|
||||
cp "$SCRIPT_DIR/compose.host-ports.yml" "$RECIPE_DIR/compose.host-ports.yml"
|
||||
echo " mumble install_steps: provided compose.host-ports.yml to recipe checkout (${CCCI_RECIPE})"
|
||||
fi
|
||||
@ -1,28 +1,33 @@
|
||||
# Per-recipe harness config for mumble (Phase 2 Q4.2 — a TCP/voice recipe, not HTTP-native).
|
||||
#
|
||||
# Mumble's voice server speaks its own TLS protocol on 64738 (no HTTP API). To fit cc-ci's
|
||||
# HTTP-readiness + on-host test model we deploy two recipe overlays:
|
||||
# HTTP-readiness + on-host test model we deploy upstream recipe overlays:
|
||||
# - compose.mumbleweb.yml -> a mumble-web HTTP client routed through Traefik on the app domain,
|
||||
# giving the generic harness a real HTTP readiness/serving signal (HEALTH_PATH "/") AND the
|
||||
# web_client.py parity surface.
|
||||
# - compose.host-ports.yml -> publishes 64738 (tcp+udp) directly on the cc-ci host (mode: host).
|
||||
# Tests run on-host (cc-ci-run), so the protocol tests connect to 127.0.0.1:64738.
|
||||
# Both overlays are shipped by the upstream recipe; this is a documented deployment mode, not a fork.
|
||||
# web_client.py parity surface. Shipped upstream from 0.1.0 (present on the 0.2.0 base too).
|
||||
# - compose.host-ports.yml -> publishes 64738 (tcp+udp, mode:host) on the cc-ci host so on-host
|
||||
# tests (cc-ci-run) reach the voice server at 127.0.0.1:64738. Shipped upstream ONLY from 1.0.0.
|
||||
#
|
||||
# F2-14c disposition (Adversary REVIEW-2 @16:22:07Z VETO): the upgrade tier's base is the previous
|
||||
# published version 0.2.0+v1.6.870-0, which PREDATES compose.host-ports.yml (added in 1.0.0). We do
|
||||
# NOT carry a cc-ci copy of that upstream overlay (no fork). Instead:
|
||||
# - the BASE 0.2.0 deploys MINIMALLY with `compose.yml:compose.mumbleweb.yml` (HTTP health via
|
||||
# mumble-web works; the voice port is NOT host-published on 0.2.0), and the on-host voice/protocol
|
||||
# custom tests are SKIPPED on 0.2.0 (they run in the CUSTOM tier, which executes once on the
|
||||
# post-upgrade LATEST);
|
||||
# - the UPGRADE to latest (1.0.0+, which ships compose.host-ports.yml NATIVELY) adds host-ports to
|
||||
# COMPOSE_FILE via UPGRADE_EXTRA_ENV (applied by generic.perform_upgrade after the PR-head
|
||||
# checkout, before the chaos redeploy), so the voice port IS host-published on latest and the
|
||||
# voice tests run there. The current version's native overlay is untouched (no cc-ci fork).
|
||||
#
|
||||
# Distinctive config markers (read back by the recipe-specific functional tests, proving our config
|
||||
# actually propagated into the running server — version-independent, not hard-coded upstream values):
|
||||
# WELCOME_TEXT -> MUMBLE_CONFIG_WELCOMETEXT, surfaced in the ServerSync welcome_text.
|
||||
# USERS -> MUMBLE_CONFIG_USERS (max users), surfaced in the ServerConfig.max_users.
|
||||
|
||||
HEALTH_PATH = "/" # mumble-web client UI
|
||||
HEALTH_PATH = "/" # mumble-web client UI (present on both 0.2.0 base and 1.0.0 latest)
|
||||
HEALTH_OK = (200,)
|
||||
|
||||
# install_steps.sh provides compose.host-ports.yml to recipe versions that predate it (the upgrade
|
||||
# tier's base deploy is the previous published version, 0.2.0+, which lacks the upstream overlay).
|
||||
# That untracked file makes abra's PINNED base-deploy clean-tree check FATA, so deploy the
|
||||
# explicitly-checked-out pinned version with chaos (skips lint/clean-tree; deploys the version, not
|
||||
# LATEST). No-op for the upgrade tier (already a PR-head chaos redeploy). See DECISIONS.md.
|
||||
CHAOS_BASE_DEPLOY = True
|
||||
DEPLOY_TIMEOUT = 900 # two images to pull (mumble-server + mumble-web) on a cold node
|
||||
HTTP_TIMEOUT = 300
|
||||
|
||||
@ -31,19 +36,34 @@ WELCOME_TEXT_MARKER = "cc-ci-mumble-welcome-7f3a9c"
|
||||
# A distinctive max-users value (not the recipe default 100) the server_config test asserts.
|
||||
MAX_USERS = 42
|
||||
|
||||
# BASE deploy (0.2.0): mumble-web only — NO host-ports (0.2.0 predates it). The voice-config env is
|
||||
# set here and persists across the upgrade so it takes effect on the latest (where the custom config
|
||||
# round-trip tests assert it).
|
||||
EXTRA_ENV = {
|
||||
"COMPOSE_FILE": "compose.yml:compose.mumbleweb.yml:compose.host-ports.yml",
|
||||
"COMPOSE_FILE": "compose.yml:compose.mumbleweb.yml",
|
||||
"WELCOME_TEXT": WELCOME_TEXT_MARKER,
|
||||
"USERS": str(MAX_USERS),
|
||||
}
|
||||
|
||||
# UPGRADE-target deploy (latest 1.0.0+): add the NATIVE compose.host-ports.yml so 64738 is
|
||||
# host-published and the on-host voice/protocol custom tests can run on latest.
|
||||
UPGRADE_EXTRA_ENV = {
|
||||
"COMPOSE_FILE": "compose.yml:compose.mumbleweb.yml:compose.host-ports.yml",
|
||||
}
|
||||
|
||||
|
||||
def READY_PROBE(domain):
|
||||
# HEALTH_PATH "/" only proves the mumble-web HTTP sidecar; it does NOT reflect the voice server.
|
||||
# After a chaos upgrade redeploy the host-mode 64738 port must be released by the old task and
|
||||
# rebound by the new one — a window where the app (voice) container isn't yet serving while
|
||||
# mumble-web still returns 200. backup-bot then execs its sqlite pre-hook into a not-running app
|
||||
# container → 409. Gate readiness on the voice port being STABLY listening (3 consecutive
|
||||
# connects) before the harness proceeds to the backup tier. The port is host-published
|
||||
# (compose.host-ports.yml), so we probe it on the cc-ci host where the run executes.
|
||||
return [{"tcp_host": "127.0.0.1", "tcp_port": 64738, "stable": 3}]
|
||||
# The voice server on 64738 is testable on-host ONLY when compose.host-ports.yml is active — i.e.
|
||||
# the post-upgrade LATEST, not the minimal 0.2.0 base. Read the live COMPOSE_FILE to decide, so the
|
||||
# SAME probe fn is correct in both phases: the post-install probe (base, no host-ports) returns []
|
||||
# (HTTP health alone gates the base), the post-upgrade probe (latest, host-ports) gates readiness
|
||||
# on the voice port being STABLY listening (3 consecutive connects) before the harness proceeds to
|
||||
# backup — after the chaos upgrade redeploy the host-mode 64738 must be released by the old task and
|
||||
# rebound by the new one (a window where mumble-web 200s while the voice container isn't yet up, and
|
||||
# backup-bot would then exec into a not-running app container -> 409).
|
||||
from harness import abra # lazy: recipe_meta is exec'd with `harness` importable at call time
|
||||
|
||||
cf = abra.env_get(domain, "COMPOSE_FILE") or ""
|
||||
if "compose.host-ports.yml" in cf:
|
||||
return [{"tcp_host": "127.0.0.1", "tcp_port": 64738, "stable": 3}]
|
||||
return []
|
||||
|
||||
@ -2,16 +2,35 @@
|
||||
install tier (which proves the mumble-web HTTP sidecar serves over Traefik — the readiness signal).
|
||||
|
||||
This overlay ADDS the assertion that mumble's actual purpose — the voice server — is up: the murmur
|
||||
control channel accepts a TLS connection on the host-published 64738 right after install. (The full
|
||||
protocol handshake + channel presence is exercised in the custom tier; here we assert the install
|
||||
produced a listening voice server, not only a web UI.)
|
||||
control channel accepts a TLS connection on the host-published 64738.
|
||||
|
||||
F2-14c: the install tier runs against the upgrade BASE, which is the previous published version
|
||||
0.2.0+v1.6.870-0. That version PREDATES compose.host-ports.yml (added upstream in 1.0.0), so the base
|
||||
deploys minimally without it and the voice port is NOT host-published — this on-host voice check is then
|
||||
not applicable on the base and is SKIPPED (recorded). The voice server is asserted listening on the
|
||||
post-upgrade LATEST via the READY_PROBE (tcp 3x, gates backup) and the custom-tier full TLS protocol
|
||||
handshake. When this overlay runs against a host-ports deploy (latest), it asserts the listening server.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||
from harness import abra # noqa: E402
|
||||
|
||||
|
||||
def test_voice_server_listening(live_app):
|
||||
cf = abra.env_get(live_app, "COMPOSE_FILE") or ""
|
||||
if "compose.host-ports.yml" not in cf:
|
||||
pytest.skip(
|
||||
"upgrade base (0.2.0) predates compose.host-ports.yml (added in 1.0.0) → voice port not "
|
||||
"host-published; voice listening asserted on post-upgrade latest (READY_PROBE tcp 3x + "
|
||||
"custom-tier protocol handshake)"
|
||||
)
|
||||
deadline = time.time() + 120
|
||||
last_err = None
|
||||
while time.time() < deadline:
|
||||
|
||||
Reference in New Issue
Block a user