|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +## What is this repository |
| 4 | + |
| 5 | +CI-Framework is an Ansible collection (`cifmw.general`) that bootstraps |
| 6 | +development and CI environments for RHOSO (Red Hat OpenStack Services on |
| 7 | +OpenShift). It is **not** intended for production or long-lived deployments. |
| 8 | + |
| 9 | +The upstream repository lives at |
| 10 | +`https://github.com/openstack-k8s-operators/ci-framework`. |
| 11 | + |
| 12 | +## Tech stack |
| 13 | + |
| 14 | +- **Ansible** collection (requires `ansible-core >= 2.15`). |
| 15 | +- **Python 3** modules and plugins under `plugins/`. |
| 16 | +- **Molecule** + Podman for per-role testing. |
| 17 | +- **ansible-test** for unit, sanity, and integration tests on plugins. |
| 18 | +- **Sphinx** for documentation (hosted on ReadTheDocs). |
| 19 | +- **Zuul**, **GitHub Actions**, and **Prow** for CI. |
| 20 | + |
| 21 | +## Repository layout |
| 22 | + |
| 23 | +| Path | Description | |
| 24 | +|---|---| |
| 25 | +| `roles/` | Ansible roles. Each has `defaults/`, `tasks/`, `molecule/`, `README.md`. | |
| 26 | +| `playbooks/` | Domain-specific playbooks in subdirectories (`adoption/`, `ceph/`, `bgp/`, etc.). Legacy numbered-stage playbooks are deprecated -- orchestration is handled by the `cifmw_setup` role. | |
| 27 | +| `plugins/` | Collection plugins: `action/`, `filter/`, `modules/`, `module_utils/`. Test with `ansible-test`, not Molecule. | |
| 28 | +| `tests/` | `ansible-test` suites: `unit/` (pytest), `integration/targets/`, `sanity/ignore.txt`. | |
| 29 | +| `ci/` | CI-only playbooks: content provider, EDPM, kuttl, architecture validation, doc build, log collection. | |
| 30 | +| `scenarios/` | Scenario-oriented variable packs used by framework flows. | |
| 31 | +| `scripts/` | Environment setup, Molecule runner, ansible-test runner, Zuul/Molecule generation, snippet checks. | |
| 32 | +| `docs/` | Sphinx sources under `docs/source/`. | |
| 33 | +| `hooks/` | Hook playbooks consumed by the framework. Some hooks have their own `roles/` subdirectory. | |
| 34 | +| `custom/` | Local overrides (gitignored except `README.md`). Safe for local dev experiments, never committed. | |
| 35 | +| `containerfiles/` | Podman images for CI (`Containerfile.ci`, `Containerfile.tests`). | |
| 36 | +| `group_vars/` | Shared group variables (e.g., `all.yml`). Changes here affect every playbook run. | |
| 37 | +| `zuul.d/` | Zuul job and project definitions. **Some files are generated -- see below.** | |
| 38 | +| `_skeleton_role_/` | Template used by `ansible-galaxy role init` when creating new roles. | |
| 39 | + |
| 40 | +## Critical rules |
| 41 | + |
| 42 | +### Variable naming |
| 43 | + |
| 44 | +All Ansible role variables **must** match the pattern `^cifmw_[a-z_][a-z0-9_]*$` |
| 45 | +where after the `cifmw_` prefix comes the role name, then the variable name |
| 46 | +(e.g., `cifmw_my_role_some_setting`). |
| 47 | +This is enforced by `ansible-lint` with `strict: true` and `profile: production`. |
| 48 | + |
| 49 | +### FQCN required |
| 50 | + |
| 51 | +All module calls must use fully-qualified collection names. |
| 52 | +The following FQCN rules are enabled in `.ansible-lint`: |
| 53 | +`fqcn-builtins`, `fqcn[action]`, `fqcn[action-core]`, `fqcn[canonical]`, `fqcn[deep]`. |
| 54 | + |
| 55 | +### Generated files -- do not hand-edit |
| 56 | + |
| 57 | +The following files are **generated** by `scripts/create_role_molecule.py`: |
| 58 | + |
| 59 | +- `zuul.d/molecule.yaml` |
| 60 | +- `zuul.d/projects.yaml` (molecule section) |
| 61 | + |
| 62 | +To regenerate: `make role_molecule`. To verify consistency: `make check_zuul_files`. |
| 63 | +If you hand-edit these files, CI will reject the change. |
| 64 | + |
| 65 | +### Read-only / generated paths |
| 66 | + |
| 67 | +Do **not** modify these paths directly: |
| 68 | + |
| 69 | +| Path | Reason | |
| 70 | +|---|---| |
| 71 | +| `zuul.d/molecule.yaml` | Generated by `scripts/create_role_molecule.py`. | |
| 72 | +| `zuul.d/projects.yaml` | Generated (molecule section). | |
| 73 | +| `custom/` | Gitignored. Local-only overrides, never committed. | |
| 74 | +| `hooks/playbooks/roles/` | Excluded from ansible-lint. Owned by hook authors. | |
| 75 | + |
| 76 | +All other paths (`roles/`, `playbooks/`, `plugins/`, `group_vars/`, `scenarios/`, |
| 77 | +`hooks/playbooks/`, `ci/`, `scripts/`, `docs/`) are safe to edit following the |
| 78 | +conventions in this file. |
| 79 | + |
| 80 | +### Debugging patterns |
| 81 | + |
| 82 | +Use `block`/`rescue`/`always` for complex task sequences. Dump relevant |
| 83 | +variables in the `rescue` block, then `ansible.builtin.fail` to stop |
| 84 | +execution. This makes CI failures much easier to diagnose. |
| 85 | + |
| 86 | +### Do not be too verbose |
| 87 | + |
| 88 | +There is no need to provide explanation for each variable in each task file. |
| 89 | +It is enough when the variable description is available in the role README |
| 90 | +file. Add comments only to complex code. |
| 91 | + |
| 92 | +### Do not create too many variables |
| 93 | + |
| 94 | +Balance the amount of variables: there is no need to create additional |
| 95 | +variables especially for static values that are only used once by another |
| 96 | +module or role. In that case, fewer variables means more clarity. |
| 97 | + |
| 98 | +### Make tasks easier to debug |
| 99 | + |
| 100 | +Tasks should not do too many things in a single step -- on failure that |
| 101 | +becomes difficult to debug. In some cases, adding more small, fast tasks |
| 102 | +is better than one large task. Add debug messages only in very complex |
| 103 | +places, not everywhere. |
| 104 | + |
| 105 | +## Playbooks |
| 106 | + |
| 107 | +Do not rely on the `playbooks/` directory as the primary orchestration layer. |
| 108 | +The numbered-stage playbooks (`01-bootstrap.yml`, `02-infra.yml`, etc.) are |
| 109 | +**deprecated** -- orchestration is now handled by the `cifmw_setup` role. |
| 110 | + |
| 111 | +The `playbooks/` directory still contains: |
| 112 | +- **Subdirectories** (`adoption/`, `ceph/`, `bgp/`, `dcn/`, `multi-namespace/`) |
| 113 | + with domain-specific flows. |
| 114 | +- **Standalone playbooks** (`hooks.yml`, `update.yml`, `dcn.yml`, `nfs.yml`, |
| 115 | + `switches_config.yml`, etc.) for specific operations. |
| 116 | + |
| 117 | +## Creating a new role |
| 118 | + |
| 119 | +Always use the Makefile: |
| 120 | + |
| 121 | +``` |
| 122 | +make new_role ROLE_NAME=my_role |
| 123 | +``` |
| 124 | + |
| 125 | +This generates the skeleton, Molecule config, and updates Zuul jobs. |
| 126 | +Every new role must have: |
| 127 | + |
| 128 | +1. A `README.md` documenting its parameters. |
| 129 | +2. Molecule test scenarios. |
| 130 | +3. Documentation that builds cleanly (checked in CI). |
| 131 | + |
| 132 | +If the role cannot be tested via Molecule, remove the `molecule/` directory |
| 133 | +and run `make role_molecule` to regenerate Zuul jobs. Add a note in the |
| 134 | +role's `README.md` explaining why. |
| 135 | + |
| 136 | +## Testing |
| 137 | + |
| 138 | +### Commands |
| 139 | + |
| 140 | +| Command | What it does | |
| 141 | +|---|---| |
| 142 | +| `make pre_commit` | Runs pre-commit hooks (shellcheck, black, ansible-lint) with dependency install. | |
| 143 | +| `make molecule` | Runs Molecule tests for all roles with dependency install. | |
| 144 | +| `make ansible_test` | Runs ansible-test (units + sanity + integration) with dependency install. | |
| 145 | +| `make tests` | Runs pre-commit + Molecule. | |
| 146 | +| `make check_zuul_files` | Regenerates Zuul YAML and fails if it differs from committed files. | |
| 147 | +| `make docs` | Builds Sphinx documentation under `docs/_build/html/`. | |
| 148 | +| `make spelling` | Runs `pyspelling` on docs. | |
| 149 | +| `make plugin-development-enable` | Rewrites import paths and sets `PYTHONPATH` for local plugin dev. | |
| 150 | +| `make plugin-development-disable` | Reverts the changes made by `plugin-development-enable`. | |
| 151 | + |
| 152 | +### Container-based testing (requires Podman) |
| 153 | + |
| 154 | +| Command | What it does | |
| 155 | +|---|---| |
| 156 | +| `make run_ctx_pre_commit` | Pre-commit in a container. | |
| 157 | +| `make run_ctx_molecule` | Molecule in a container. | |
| 158 | +| `make run_ctx_ansible_test` | ansible-test in a container. | |
| 159 | +| `make run_ctx_all_tests` | All of the above. | |
| 160 | + |
| 161 | +### Molecule specifics |
| 162 | + |
| 163 | +- Config: `.config/molecule/config_podman.yml` (host) or `config_local.yml` (container). |
| 164 | +- Test a single role: `TEST_SINGLE_ROLE=my_role make molecule` or `make run_ctx_molecule`. |
| 165 | +- Molecule scenarios live under `roles/<name>/molecule/`. |
| 166 | + |
| 167 | +### Validation priority |
| 168 | + |
| 169 | +When verifying a change, run checks in this order: |
| 170 | + |
| 171 | +1. `pre-commit run --all-files` — fast lint pass (ansible-lint, black, shellcheck). |
| 172 | +2. `TEST_SINGLE_ROLE=<role> make molecule` — targeted Molecule test. |
| 173 | +3. `make ansible_test` — plugin unit/sanity/integration tests (only if plugins changed). |
| 174 | +4. `make docs` — only if documentation was modified. |
| 175 | + |
| 176 | +### macOS limitations |
| 177 | + |
| 178 | +The Makefile test targets (`make tests`, `make molecule`, `make ansible_test`, |
| 179 | +`make setup_tests`, `make setup_molecule`, and their `_nodeps` variants) **do not |
| 180 | +work on macOS**. The underlying scripts use `readlink -f`, which is not available |
| 181 | +on macOS. These targets are designed for Linux CI environments only. |
| 182 | + |
| 183 | +## Linting and code style |
| 184 | + |
| 185 | +- **ansible-lint**: `production` profile, `strict: true`. Config in `.ansible-lint`. |
| 186 | +- **Python**: Formatted with `black`. |
| 187 | +- **Shell**: Checked with `shellcheck` (severity=error, excludes SC2071). |
| 188 | +- **Pre-commit**: Config in `.pre-commit-config.yaml`. Run with `make pre_commit` or `make run_ctx_pre_commit`. |
| 189 | +- **Spelling**: `pyspelling` on docs. Run with `make spelling`. |
| 190 | + |
| 191 | +### Excluded from linting |
| 192 | + |
| 193 | +ansible-lint skips: `.github/`, `scripts/`, `docs/`, `containerfiles/`, `ci/`, |
| 194 | +and the generated Zuul files (`zuul.d/projects.yaml`, `zuul.d/molecule.yaml`). |
| 195 | + |
| 196 | +## Commit conventions |
| 197 | + |
| 198 | +- **Title**: Must begin with the role name in brackets or parentheses: |
| 199 | + `[my_role] Add feature X` or `(my_role) Fix bug Y`. |
| 200 | + If changes span multiple roles, use `[multiple]` or `(multiple)`. |
| 201 | + For cross-cutting changes use a category: `[ci]`, `[docs]`, `[Feature]`. |
| 202 | +- **Body**: Must be longer than 10 characters and describe **why** the change |
| 203 | + was made. |
| 204 | +- **Sign-off**: Required (`git commit --signoff`). The sign-off certifies a |
| 205 | + [DCO](https://developercertificate.org/). AI agents cannot sign off on behalf |
| 206 | + of a human -- the committer must add it themselves or amend the commit. |
| 207 | +- **AI attribution**: Use `Co-Authored-By:` for substantial AI-generated code, |
| 208 | + `Assisted-By:` for minor AI help. Disclose the scope in the PR description. |
| 209 | +- **Ticket references**: Link Jira cards in the commit message body: |
| 210 | + `Closes: ANVIL-123` (resolves the ticket) or |
| 211 | + `Related-Issue: #OSPRH-12345` (related but does not close). |
| 212 | +- **Cross-repo dependencies**: When a change depends on an unmerged PR/MR in |
| 213 | + another repository, add `Depends-On: <PR-or-MR-URL>` in the PR/MR |
| 214 | + description. Zuul uses this to test the changes together. |
| 215 | + |
| 216 | +### Commit strategy |
| 217 | + |
| 218 | +To keep a clean git history, prefer a single commit per feature or fix: |
| 219 | + |
| 220 | +1. Create the initial commit normally. |
| 221 | +2. For subsequent changes on the same branch, amend the existing commit |
| 222 | + (`git commit --amend`) instead of creating new ones. |
| 223 | +3. After amending, use `git push --force` to update the remote branch. |
| 224 | + |
| 225 | +Never push directly to `main` — it is a protected branch. Always work on |
| 226 | +a feature branch. Force pushing is only appropriate for **solo feature |
| 227 | +branches**, never for `main` or shared branches. |
| 228 | + |
| 229 | +If a change is not directly related to the main goal of the pull request |
| 230 | +but is required for it to work, add it as a **separate commit**. When |
| 231 | +amending, be careful to edit only the commits that belong to the same |
| 232 | +pull request. |
| 233 | + |
| 234 | +## Branch workflow |
| 235 | + |
| 236 | +- The default branch is `main`. |
| 237 | +- Feature work happens on topic branches. |
| 238 | +- PRs target `main` unless otherwise specified. |
| 239 | +- Branch names should be descriptive (e.g., `fix-reproducer-pull-secret`, |
| 240 | + `feature/OSPRH-12345-new-role`). |
| 241 | + |
| 242 | +## PR process |
| 243 | + |
| 244 | +- PRs are auto-set to draft on open. To undraft, push a non-`nit:` change. |
| 245 | +- Minimum **2 approvals** required (excluding the author). |
| 246 | +- Security-sensitive code requires additional maintainer review. |
| 247 | +- Ownership is defined in `OWNERS` and `OWNERS_ALIASES`. |
| 248 | + |
| 249 | +## Relationship to ci-framework-jobs |
| 250 | + |
| 251 | +The `ci-framework-jobs` repository holds downstream Zuul job definitions that |
| 252 | +consume this repository. Jobs in that repo declare |
| 253 | +`required-projects: openstack-k8s-operators/ci-framework` and |
| 254 | +`roles: zuul: openstack-k8s-operators/ci-framework` so Zuul checks out this |
| 255 | +repo and exposes its roles during job execution. Uni jobs orchestrate this |
| 256 | +repo's `reproducer.yml` playbook as their main entry point. |
| 257 | + |
| 258 | +When making changes here that affect CI behavior, coordinate with the |
| 259 | +corresponding job definitions in `ci-framework-jobs`. |
| 260 | + |
| 261 | +## Plugin development |
| 262 | + |
| 263 | +To develop collection plugins locally without installing the collection: |
| 264 | + |
| 265 | +``` |
| 266 | +make plugin-development-enable |
| 267 | +``` |
| 268 | + |
| 269 | +This rewrites import paths and sets `PYTHONPATH`. Revert with: |
| 270 | + |
| 271 | +``` |
| 272 | +make plugin-development-disable |
| 273 | +``` |
| 274 | + |
| 275 | +Plugins are tested with `ansible-test`, not Molecule. |
| 276 | + |
| 277 | +## Documentation first |
| 278 | + |
| 279 | +Before searching the web or relying on general knowledge, check local |
| 280 | +documentation: |
| 281 | +- `docs/source/` — Sphinx sources for the ci-framework collection. |
| 282 | +- Role-level `README.md` files under `roles/<name>/`. |
| 283 | +- The downstream CI docs repository at |
| 284 | + `https://gitlab.cee.redhat.com/ci-framework/docs` covers job types, |
| 285 | + pipelines, troubleshooting, and glossary. |
| 286 | + |
| 287 | +## Confirm before acting |
| 288 | + |
| 289 | +Before performing expensive or broad-impact operations, confirm with the |
| 290 | +user first: |
| 291 | + |
| 292 | +- Running full test suites (`make tests`, `make molecule`) — ask whether a |
| 293 | + targeted run (`TEST_SINGLE_ROLE=<role>`) is sufficient. |
| 294 | +- Modifying `group_vars/all.yml` — changes here affect every playbook run. |
| 295 | +- Editing roles used by multiple playbooks — flag the blast radius. |
| 296 | +- Cross-repo changes that require coordinated updates in `ci-framework-jobs` |
| 297 | + or `architecture`. |
0 commit comments