diff --git a/CHANGELOG.md b/CHANGELOG.md index bc24eba..d8ff1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). source of the Azure / Google / Microsoft egress URLs in `websites.yml`. ### Changed +- Documentation caught up to code: README repo-layout tree + updated + `schemes` example + Output section reflecting the firewall-rule punch + list; `docs/install-manual.md` "(coming soon)" reference dropped now + that `install-with-ai.md` ships; `CLAUDE.md` updated file table, CI + lint scope, and `ansible.builtin`-only scoping (probe playbook only, + not the loader); `CONTRIBUTING.md` branch-naming pattern aligned with + practice and failure-path test pattern documented via `-e @`. - CI `yamllint` and `ansible-lint` workflows now also lint `aap_config/`. - `playbooks/files/websites.yml` schema comment now says `schemes` is required, instead of the aspirational "defaults to [https] if omitted" diff --git a/CLAUDE.md b/CLAUDE.md index 5e8cf8b..9421360 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,12 +6,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Ansible playbook that probes a list of upstream URLs and reports which (if any) are unreachable. Useful for verifying egress before installing software that pulls from those endpoints — Red Hat container registries, Azure CDNs, Google APIs, Let's Encrypt, GitHub, and so on. -Single playbook, single data file: +The intended customer scenario is Red Hat Managed AAP on Azure: the playbook is loaded *into* the customer's AAP via the configuration-as-code in `aap_config/` and run as a Job Template to verify the Azure firewall rules permit AAP's required egress. -| File | Purpose | +| Path | Purpose | |------|---------| -| `playbooks/main.yml` | Probes each URL on its declared scheme(s), prints a summary, fails non-zero on any miss | -| `playbooks/files/websites.yml` | The URL list — `url`, `method`, expected `status`, and `schemes` per entry | +| `playbooks/main.yml` | The probe playbook. Iterates each URL on its declared scheme(s), prints a summary and a deduplicated firewall-rule punch list, fails non-zero on any miss. Uses `ansible.builtin` only. | +| `playbooks/files/websites.yml` | The URL list — `url`, `method`, expected `status`, and `schemes` per entry. KB-derived rationale in the file header. | +| `aap_config/` | Config-as-code to load the probe into a customer's AAP as a Project + Job Template. Uses `infra.aap_configuration`. Pinned in `aap_config/requirements.yml`. | +| `.claude/skills/url-checker-install/` | Claude Code skill that walks an operator through the AAP install interactively. Token is held in shell env only — never persisted. | +| `docs/install-manual.md` | Customer-facing manual install path. | +| `docs/install-with-ai.md` | Customer-facing AI-assisted install path. | ## Running @@ -19,7 +23,7 @@ Single playbook, single data file: ansible-playbook playbooks/main.yml ``` -Runs against `localhost` with `connection: local` — no inventory needed. Only the `ansible.builtin` collection is used, which ships with `ansible-core`, so there is nothing to install from Galaxy. +Runs against `localhost` with `connection: local` — no inventory needed. The probe playbook uses `ansible.builtin` only and runs from a fresh box with `ansible-core`. The AAP loader in `aap_config/load.yml` is the one path that requires an extra collection — see `aap_config/requirements.yml`. ## Adding or Updating a URL @@ -30,23 +34,25 @@ Edit `playbooks/files/websites.yml`. Each entry has four fields: | `url` | Hostname (or host + path) to probe | | `method` | HTTP method: `HEAD`, `GET`, `OPTIONS`, etc. | | `status` | Expected HTTP status code (integer) | -| `schemes` | List of schemes to probe — e.g. `[https]` or `[http, https]` | +| `schemes` | List of schemes to probe — default `[http, https]` per KB 6972355 | Pick the lightest method an endpoint accepts (`HEAD` whenever possible) and set `status` to whatever that endpoint actually returns on a probe of its root — some hosts answer `400` or `404` on `/` and that is still a valid reachability signal. Document any non-obvious expected status with an inline comment so future editors don't "fix" it. +The play uses `follow_redirects: all`, so an http probe that 301s to https resolves to the final https status — the same `status` value works for both schemes on every entry in the current list. Drop to `[https]` only if the host genuinely does not answer on port 80. + ## Key Conventions - **One concern per PR** — URL list edits, playbook behavior changes, and docs changes each go in their own issue/PR unless they share a root cause. - **Document before fixing** — open a GitHub issue describing the change before writing code. - **CHANGELOG.md is mandatory** — every PR adds an entry under `## [Unreleased]` using Keep-a-Changelog sections (`Added`, `Changed`, `Removed`, `Fixed`). - **Failure path stays loud** — the playbook must exit non-zero on any URL miss. Do not reintroduce `ignore_errors: yes` at the play level. -- **`ansible.builtin` only** — keep this repo zero-dependency so it can run from a fresh box without Galaxy/Hub setup. +- **`ansible.builtin` only in `playbooks/`** — the probe playbook stays zero-dependency so it runs from a fresh box without Galaxy/Hub setup. The loader in `aap_config/` is the one place an extra collection (`infra.aap_configuration`) is pulled in. ## CI GitHub Actions runs on every PR: -- `yamllint` against `playbooks/` and the repo root -- `ansible-lint` against `playbooks/` +- `yamllint` against `playbooks/`, `aap_config/`, the repo root, and `.github/` +- `ansible-lint` against `playbooks/` and `aap_config/` A scheduled workflow (`url-check.yml`) runs the playbook itself on a weekly cron and opens a GitHub issue when any probe fails — the repo is its own monitor. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a00729..664b959 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ Open issue → branch from main → implement → open PR (Closes #N) → merge ``` 1. **Open an issue first** — before writing a single line of code or making any change, open a GitHub issue describing what you're fixing or adding and why. No implementation without an issue. One issue per change. -2. **Branch from `main`** — use the naming pattern `/` (e.g. `fix/azureedge-status`, `feat/scheduled-check`). +2. **Branch from `main`** — use the naming pattern `-` (e.g. `12-firewall-rule-output`, `10-add-http-schemes`). Issue-prefixed branches make traceability one-click from any commit. 3. **One concern per PR** — implement, commit, and open a PR for each issue separately. Group changes by shared root cause, not by item count. 4. **Reference the issue** — include `Closes #` in your PR description so the issue closes automatically on merge. 5. **PRs target `main`** — direct pushes to `main` are not allowed. @@ -42,7 +42,24 @@ Run the playbook locally before opening a PR: ansible-playbook playbooks/main.yml ``` -If you changed playbook behavior (not just the URL list), also verify the failure path by temporarily setting an entry's expected `status` to a value the host won't return, and confirm the play exits non-zero with a readable failure line. Document how you tested in the PR description. +If you changed playbook behavior (not just the URL list), also verify the failure path. The cleanest pattern is to override the `websites` variable via `-e @`, which beats `vars_files` in precedence — no file edits required: + +```bash +cat > /tmp/bad-websites.yml <<'EOF' +websites: + - url: nonexistent-host-abc-12345.invalid + method: HEAD + status: 200 + schemes: [http, https] +EOF + +ansible-playbook playbooks/main.yml -e @/tmp/bad-websites.yml +# Confirm exit code is non-zero and the firewall-rule punch list renders correctly +``` + +If you touched `aap_config/`, additionally verify the loader against a live AAP — installing the collection, running `aap_config/load.yml` with `AAP_HOSTNAME`/`AAP_TOKEN` set, and confirming the Project + Job Template land. The `url-checker-install` skill at `.claude/skills/` walks through this end-to-end. + +Document how you tested in the PR description. ## CHANGELOG diff --git a/README.md b/README.md index 087a759..f49882c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,21 @@ Ansible playbook that verifies a list of external URLs is reachable from whereve the play runs. Useful for confirming that a host (or a network segment) has the egress it needs before installing software that pulls from those endpoints. +## Repo layout + +``` +playbooks/ + main.yml — the probe playbook + files/websites.yml — the URL list (data only) +aap_config/ — config-as-code to load the probe into a customer's AAP + load.yml, requirements.yml, inventory/, group_vars/, files/ +.claude/skills/ + url-checker-install/ — Claude Code skill for the AI-assisted install +docs/ + install-manual.md — laptop/desktop install path for the AAP CaC + install-with-ai.md — AI-assisted install path +``` + ## Run it ```bash @@ -43,13 +58,20 @@ Example: - url: example.com method: HEAD status: 200 - schemes: [https] + schemes: [http, https] ``` +Most entries currently list `[http, https]` per Red Hat KB 6972355, which calls +for both ports to be open at the Azure firewall. Drop to `[https]` only if +the host genuinely does not answer on port 80 (none of the entries today are +in that category). + Pick the lightest method the endpoint accepts (`HEAD` whenever possible) and set `status` to whatever that endpoint actually returns — some endpoints answer `404` or `403` on the root path, and that is still a valid signal that the host -is reachable. +is reachable. The play has `follow_redirects: all`, so an http probe that 301s +to https resolves to the final https status — one `status` value covers both +schemes on every entry in the current list. ## Output @@ -59,8 +81,28 @@ On success the play prints a one-line summary: N of N URL probes passed. ``` -On failure each failed probe is listed with the URL, method, expected status, -and what was actually returned, then the play fails with a non-zero exit code. +On failure each failed probe is printed with the URL, method, expected status, +the actual response, and the firewall rule the customer needs to add — for +example: + +``` +FAIL: https://acs-mirror.azureedge.net (HEAD) + expected 400 got no response - Connection refused + -> Add egress rule: allow tcp/443 to acs-mirror.azureedge.net +``` + +At the end of the play, a deduplicated punch list of every rule to add is +printed: + +``` +Firewall rules to add (N unique): + - allow tcp/443 to acs-mirror.azureedge.net + - allow tcp/80 to crl.microsoft.com + - ... +``` + +The play then fails with a non-zero exit code so the scheduled CI cron +opens a tracking issue automatically. ## References diff --git a/docs/install-manual.md b/docs/install-manual.md index d01895c..a76f69b 100644 --- a/docs/install-manual.md +++ b/docs/install-manual.md @@ -9,7 +9,7 @@ runnable on demand to verify the firewall rules in front of AAP permit its required egress. If you'd rather have an AI agent walk you through this, see -[install-with-ai.md](install-with-ai.md) *(coming soon)*. +[install-with-ai.md](install-with-ai.md). ## Prerequisites