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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 @<fixture>`.
- 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"
Expand Down
24 changes: 15 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@ 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

```bash
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

Expand All @@ -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.
21 changes: 19 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<type>/<short-description>` (e.g. `fix/azureedge-status`, `feat/scheduled-check`).
2. **Branch from `main`** — use the naming pattern `<issue#>-<short-description>` (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 #<number>` in your PR description so the issue closes automatically on merge.
5. **PRs target `main`** — direct pushes to `main` are not allowed.
Expand Down Expand Up @@ -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 @<fixture>`, 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

Expand Down
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/install-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading