Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,24 @@ jobs:
# own venvs. ansible-lint pulls ansible-core into its venv itself.
pipx install ansible-core
pipx install ansible-lint
# ansible-core ships no community collections, so without this the
# roles' non-builtin FQCNs resolve locally (requirements.txt pins the
# batteries-included `ansible` package) and fail here.
ansible-galaxy collection install -r ansible/requirements.yml
# The roles' non-builtin FQCNs resolve locally because requirements.txt
# pins the batteries-included `ansible` package; here they have to be
# fetched - into one path every later step agrees on. Each tool has
# its own default view of where collections live: the runner ships
# some preinstalled, and ansible-lint runs from its own pipx venv and
# reads neither those nor ansible-core's. Without the explicit -p and
# --force, galaxy finds the runner's copies, reports "nothing to do",
# and ansible-lint still cannot resolve community.general.
#
# Exported through GITHUB_ENV rather than the job's env: block
# because that block cannot see the runner context. Singular _PATH -
# ansible-compat hard errors on the plural spelling - and outside the
# workspace, so ansible-lint never walks it as project files.
collections="$RUNNER_TEMP/ansible-collections"
echo "ANSIBLE_COLLECTIONS_PATH=$collections" >>"$GITHUB_ENV"
ansible-galaxy collection install -r ansible/requirements.yml \
-p "$collections" --force
ansible-galaxy collection list -p "$collections"

- name: copy examples
run: |
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ private/**
!private/.gitkeep
# scratch
tmp/
# ansible-lint's cache and its symlinked copy of the project, written into
# whichever directory holds .git - so the repo root, whatever `ansible-lint`
# is run from.
.ansible/
1 change: 1 addition & 0 deletions AGENTS.md
206 changes: 127 additions & 79 deletions CLAUDE.md

Large diffs are not rendered by default.

26 changes: 12 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,36 @@ or tokens.
With a ProxMox node of your own: copy `terraform.tfvars.example` to
`terraform.tfvars`, fill in the `EDIT` values (see README.md for the full
setup, including `secrets.enc.json` — layout in `secrets.enc.json.example`),
then `source tofu.env` and use `inventory/example.yaml` as a starting spec.
then `source tofu.env` and use `inventory-example.yaml` as a starting spec.
You will also need a golden image on the node — `./scripts/build-image.sh
--upload` builds and uploads one; see README.md, "Building the golden image".

Without a node, run what CI runs:

```sh
export TF_VAR_state_passphrase=ci-dummy-passphrase-render-only
tofu init -input=false
tofu validate
./scripts/check-cloud-init.sh # needs: cloud-init, python3-yaml
ansible-galaxy collection install -r ansible/requirements.yml
ansible-playbook ansible/site.yaml --syntax-check -i localhost,
(cd ansible && ansible-lint)
```

The dummy passphrase satisfies the state-encryption config for read-only
rendering; no state is created or read.

Notes on the two check scripts:

- `check-cloud-init.sh` renders the real cloud-init output and validates it —
`tofu validate` alone never sees the YAML that comes out of
`templatefile()`. Run it after touching anything under `cloud-init/`.
- `check-ansible.sh` needs an applied state (it inspects the live inventory),
so it only runs against a real environment; CI covers the state-independent
parts (syntax check and lint). Run the full script if you have a node.
`check-ansible.sh` needs an applied state (it inspects the live inventory), so
it only runs against a real environment; CI covers the state-independent parts
(syntax check and lint). Run the full script if you have a node.

## Style

- Every scalar interpolated into a cloud-init template must be
`jsonencode()`d — see the comments in `cloud-init/base.yaml.tftpl` and the
adversarial pass in `check-cloud-init.sh` that regresses this.
- Any collection beyond `ansible.builtin` must be pinned in
`ansible/requirements.yml` in the same change that uses it. CI installs
`ansible-core`, which ships none, so an unpinned `community.*` task passes
locally and fails there.
- Ansible role names use underscores; each is a directory under
`ansible/roles/`. Pin software versions in the role's `defaults/main.yaml`.
- Keep guard behavior intact: `guards.tofu` and the validations in
`modules/vm/variables.tofu` exist to protect pre-existing VMs and must not
`modules/vm-pve/variables.tofu` exist to protect pre-existing VMs and must not
be weakened.
Loading
Loading