diff --git a/AGENTS.md b/AGENTS.md index 17e2f78..e37bd97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,6 +80,7 @@ make molecule # containerised converge/verify of the decdn_node role (ne cd ansible make deps # vendor pinned Galaxy collections into ./collections make check / deploy # deCDN node (site.yml): dry-run / provision + # fleet-wide by default; LIMIT= scopes, ANSIBLE_ARGS='…' passes through make build / galaxy-check # stage + build the decdn.node collection, then validate it ``` diff --git a/README.md b/README.md index 47f52a6..db442de 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ make security # KICS IaC scan of ansible/ (pinned engine image) cd ansible make deps # vendor pinned Galaxy collections into ./collections make check / deploy # deCDN node (site.yml): dry-run / provision + # fleet-wide by default; LIMIT= scopes, ANSIBLE_ARGS='…' passes through ``` **Gotcha — pre-commit is local-only.** Hygiene/shellcheck/yamllint/markdown run via diff --git a/ansible/Makefile b/ansible/Makefile index c1db30d..b7dbcc7 100644 --- a/ansible/Makefile +++ b/ansible/Makefile @@ -14,13 +14,53 @@ lint: ansible-lint # --- Primary: the public deCDN node ------------------------------------------ +# Optional scoping for the deploy targets. Both are empty by default, so check/deploy +# stay fleet-wide (every host in decdn_nodes) unless you say otherwise. +# make deploy LIMIT=decdn-node-1 # one host +# make deploy LIMIT='decdn-node-1,decdn-node-2' # a list +# make deploy ANSIBLE_ARGS='--tags decdn_node -vv' +# +# Quote both values at YOUR prompt, not just in the examples: +# - make expands the --limit value single-quoted, so ansible host patterns reach +# ansible intact — but your own shell runs first, and eats an unquoted '!node' +# (history expansion) or 'decdn-node-*' (globbing) before make ever starts. +# - ANSIBLE_ARGS is substituted unquoted on purpose: the recipe's shell splits it +# back into separate flags (so inner quoting works — +# ANSIBLE_ARGS='--start-at-task="Install the decdn binaries"' survives). Unquoted +# at your prompt, make takes the extra words as goals and flags of its own — a +# bare -vv is make's own -v. A literal $ must be written $$. +LIMIT ?= +ANSIBLE_ARGS ?= +limit_arg = $(if $(LIMIT),--limit '$(LIMIT)') + +# Fail loud on the two ways a run can look scoped but silently be fleet-wide. Scoped to +# check/deploy via MAKECMDGOALS so an unrelated `make lint` is never blocked. NOTE: this +# does not catch an argument that is well-formed but selects nothing — `--tags` with a +# typo (decdn-node vs decdn_node) runs zero tasks and still exits 0. See ansible/README.md. +ifneq ($(filter check deploy,$(MAKECMDGOALS)),) + # Echo back the goal the operator actually typed, so the hint never nudges a + # `make check` dry-run toward the mutating `make deploy`. + scoped_goal := $(firstword $(filter check deploy,$(MAKECMDGOALS))) + ifeq ($(origin LIMIT),environment) + $(error LIMIT is set in your environment ($(LIMIT)) and would silently scope this run. Unset it or pass it on the command line: make $(scoped_goal) LIMIT=) + endif + ifeq ($(origin ANSIBLE_ARGS),environment) + $(error ANSIBLE_ARGS is set in your environment and would silently alter this run. Unset it or pass it on the command line) + endif + ifeq ($(origin LIMIT),command line) + ifeq ($(strip $(LIMIT)),) + $(error LIMIT was supplied but expanded empty (unset shell variable?) - refusing to run fleet-wide by accident. Omit LIMIT entirely to mean "all hosts") + endif + endif +endif + # Dry run against the real inventory — shows diffs, mutates nothing. check: - ansible-playbook playbooks/site.yml --check --diff + ansible-playbook playbooks/site.yml --check --diff $(limit_arg) $(ANSIBLE_ARGS) # Provision the deCDN node host(s) in inventory/hosts.yml. deploy: - ansible-playbook playbooks/site.yml + ansible-playbook playbooks/site.yml $(limit_arg) $(ANSIBLE_ARGS) # --- Tests ------------------------------------------------------------------- # Containerised converge + idempotence + verify of the decdn_node role against a diff --git a/ansible/README.md b/ansible/README.md index 8e8ab8a..0d95176 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -106,6 +106,45 @@ make check # dry run (--check --diff); asserts fire if required knobs a make deploy # provision + start the node ``` +Both targets are **fleet-wide by default** — every host in `decdn_nodes`. Scope a run with +`LIMIT`: + +```bash +make check LIMIT=decdn-node-1 # dry-run one host +make deploy LIMIT=decdn-node-1 # converge one host +make deploy LIMIT='decdn-node-1,decdn-node-2' # a list +make deploy LIMIT='!decdn-node-2' # any ansible host pattern +``` + +Quote the pattern at your own prompt — your shell runs before make does, and eats a bare +`!decdn-node-2` (history expansion) or `decdn-node-*` (globbing). + +Use `LIMIT` when **bringing up a new node**: that host's first converge creates the +operator account and then lets `ssh_hardening` disable root + password login — after which +you switch its `ansible_user` by hand (see [Setup](#setup) above; the converge does not +edit your inventory). You do not want that play re-converging nodes already serving +paid traffic. Same when re-running a single node after a config change, a failed play, or a +binary bump. + +`make check`/`make deploy` refuse to run if `LIMIT` or `ANSIBLE_ARGS` reaches them from an +exported shell variable, or if `LIMIT` expands empty — both are ways a run looks scoped but +is silently fleet-wide. + +`ANSIBLE_ARGS` passes anything else straight through. Quote the whole value at your prompt, +or make will read the extra words as its own goals and flags (a bare `-vv` is make's `-v`); +the recipe's shell then splits it back into separate flags, so inner quoting works. Write a +literal `$` as `$$`: + +```bash +make deploy LIMIT=decdn-node-1 ANSIBLE_ARGS='--tags decdn_node -vv' +make deploy LIMIT=decdn-node-1 ANSIBLE_ARGS='--start-at-task="Install the decdn binaries"' +``` + +> **Watch the PLAY RECAP.** A *well-formed* argument that selects nothing is not an error: +> `--tags decdn-node` (hyphen, vs the real `decdn_node`) runs zero tasks and still exits 0, +> as does a `--limit` matching a host outside `decdn_nodes`. A malformed flag fails loudly; +> these do not. Confirm the recap lists the hosts you expected. + Then confirm: ```bash