mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-11 13:17:07 +00:00
New ``cli.administration.deploy.swarm`` package wraps the strict
``cli.administration.deploy.dedicated`` runner with a layered closure
so swarm deploys never require operators (or test scripts) to
enumerate every infra role:
effective --id = seed (operator --id, else inventory_role_groups)
∪ dep-walk(seed via applications_if_group_and_all_deps)
∪ default-placement safety net
The dep-walker is the same SPOT
(``utils.roles.applications.in_group_deps.applications_if_group_and_all_deps``)
the runtime constructor consumes via ``lookup('applications_current_play')``,
so the CLI-side closure and the in-play role set stay in sync.
The dedicated CLI keeps strict explicit-only semantics: ``main`` was
split into ``parse_args`` + ``run_from_args`` so the swarm wrapper
can share parser, mode wiring, validator and runner verbatim and
only mutate ``args.id``.
Meta-side: ``svc-db-postgres`` gains ``default_placement: manager``
(semantic symmetry with mariadb). ``default_placement`` is now a
PLACEMENT hint only ("if deployed, run on the manager"); deploy
decisions come from the dep-walk. Apps that declare ``mariadb:``
pull svc-db-mariadb; apps that declare ``postgres:`` pull
svc-db-postgres; apps that declare neither get no central DB.
Shared utils extracted so the four consumers stay aligned:
* ``utils.inventory.groups.inventory_has_group``: YAML + INI static
inventory group probe (extracted from ValidDeployId).
* ``utils.roles.meta_lookup.{get_role_default_placement,
iter_roles_with_default_placement}``: single source of truth for
``default_placement`` discovery (lookup plugin is now a thin wrapper).
Test pipeline (``scripts/tests/deploy/swarm/*``):
* Heredoc-Python shells converted to proper modules under
``utils.tests.swarm.*`` and invoked via ``python -m``:
- extend_inventory.py (was 08_extend_inventory.sh)
- write_extras.py (was 09_write_extras.sh dynamic half)
- derive_includes.py (replaces 06's hardcoded includes array)
- write_hosts_entries.py (replaces 02's hardcoded /etc/hosts heredoc;
derives every canonical + www-redirect domain from APP_ID via
canonical_domains_map + generate_all_domains, so the test
works for any app in the matrix without per-app maintenance)
* 01_create_network.sh deleted (single docker command inlined in workflow).
* 11_deploy.sh deleted (swarm CLI now called directly in workflow).
* 09_write_extras.sh: static half moved to inventories/development/swarm.yml.
* Remaining shell scripts renumbered 01-13 without gaps.
* 08_wait_converge.sh: case block on DB_DEP replaced with single
conditional (container name == DB_DEP).
* 06_provision_inventory.sh: hardcoded includes array replaced with
`mapfile -t includes < <(python -m utils.tests.swarm.derive_includes)`.
* Workflow YAML step references updated to new script names + module
invocations.
New tests (39 covering the closure surface):
* tests/unit/cli/administration/deploy/swarm/test_closure.py (14)
* tests/unit/utils/inventory/test_groups.py (11)
* tests/unit/utils/roles/test_meta_lookup.py (+3 for default_placement)
* tests/unit/utils/roles/validation/test_deploy_id.py (unchanged
8 tests still pass after refactor onto the shared util).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
56 lines
1.5 KiB
Bash
Executable File
56 lines
1.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
HOST_SRC="$(pwd)"
|
|
|
|
HOST_SUDO=""
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
HOST_SUDO="sudo -E"
|
|
fi
|
|
|
|
echo "==> Building .deb once on the act-runner"
|
|
DEB_PATH=$(PACKAGE_BUILD_ONLY=1 ${HOST_SUDO} bash "${HOST_SRC}/scripts/install/package.sh" |
|
|
tee /dev/stderr | tail -n1)
|
|
[ -f "${DEB_PATH}" ] || {
|
|
echo "FAILURE: builder did not produce a .deb (${DEB_PATH})"
|
|
exit 1
|
|
}
|
|
echo "==> built ${DEB_PATH}"
|
|
|
|
DEB_BASENAME="$(basename "${DEB_PATH}")"
|
|
|
|
provision_node() {
|
|
local node="$1"
|
|
echo "==> [${node}] copying repo + .deb + installing"
|
|
docker exec "${node}" mkdir -p /opt/infinito-nexus
|
|
tar -C "${HOST_SRC}" -cf - . | docker exec -i "${node}" tar -C /opt/infinito-nexus -xpf -
|
|
# docker cp to a tmpfs mount lands in the overlay shadow path (moby/22281).
|
|
docker exec -i "${node}" sh -c "cat > /tmp/${DEB_BASENAME}" <"${DEB_PATH}"
|
|
docker exec \
|
|
-e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 \
|
|
-e PACKAGE_INSTALL_FROM="/tmp/${DEB_BASENAME}" \
|
|
"${node}" bash -c \
|
|
'cd /opt/infinito-nexus && bash scripts/install/package.sh'
|
|
}
|
|
|
|
declare -A PIDS
|
|
for node in "${MGR}" "${WRK1}" "${WRK2}" "${NFS_SERVER}"; do
|
|
provision_node "${node}" >"/tmp/bootstrap-${node}.log" 2>&1 &
|
|
PIDS["${node}"]=$!
|
|
done
|
|
|
|
rc=0
|
|
for node in "${!PIDS[@]}"; do
|
|
if ! wait "${PIDS[${node}]}"; then
|
|
echo "FAILURE: provision_node ${node} exited non-zero"
|
|
rc=1
|
|
fi
|
|
done
|
|
|
|
for node in "${MGR}" "${WRK1}" "${WRK2}" "${NFS_SERVER}"; do
|
|
echo "---- bootstrap log for ${node} ----"
|
|
cat "/tmp/bootstrap-${node}.log"
|
|
done
|
|
|
|
exit "${rc}"
|