diff --git a/AGENTS.md b/AGENTS.md index 0aa5dde..f5bdba2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,74 @@ The pre-commit hook runs actionlint automatically when workflow files change. The CI `validate` job runs `make setup-image` then `make lint-all`. CI also runs a best-effort `make workflow-dryrun` step (skipped if act_runner is not installed in the CI Docker image). +## Composite Actions (`.gitea/actions/`) + +Reusable Gitea composite actions eliminate repeated multi-step sequences +across workflows. Each action lives in its own directory under +`.gitea/actions//action.yml` and is referenced via +`uses: ./.gitea/actions/`. + +### Available Composite Actions + +| Action | Purpose | Inputs | +|--------|---------|--------| +| `setup-env` | Run `make setup-image` (with optional `EXTRAS=`) | `extras` (default: `""`) | +| `notify-failure` | Create a Gitea issue on job failure via `devx.ci.notify_failure` | `workflow` (required) | +| `quality-checks` | 6-step quality sequence: lint, tests, speed, docs, translations, security | `package`, `test-speed-max`, `test-speed-max-single`, `translations-file` | + +### Gitea 1.27 Constraints + +- Every `run` step in a composite action MUST have explicit `shell:`. +- Composite actions CANNOT access `secrets` directly. They read from + the calling workflow's `env:` context (for example, `${{ env.CI_GITEA_API_TOKEN }}`). + The calling workflow's top-level `env:` block must define the required + env vars. +- `if: failure()` in a composite action step is evaluated in the + calling workflow's job-status context. + +### Usage Pattern + +```yaml +jobs: + validate: + env: + CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} + CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }} + steps: + - uses: actions/checkout@v4 + - uses: ./.gitea/actions/setup-env + - uses: ./.gitea/actions/quality-checks + with: + package: devx + - uses: ./.gitea/actions/notify-failure + with: + workflow: "ci/validate" +``` + +### Same-Repo Copies (No Cross-Repo References) + +Each repo (`devx`, `grm`, `infra`) gets its own copy of the composite +actions under its `.gitea/actions/` directory. There is no +`uses: oblachno-oss/devx/.gitea/actions/...@vX.Y.Z` reference. This +avoids a single-point-of-failure where a bad devx master commit would +break all repos' CI simultaneously. See ADR-0003 for the full +rationale. + +### When NOT to Use Composite Actions + +- **`make setup-release` / `make setup-ci`**: the `setup-env` action + only wraps `make setup-image`. Workflows that use other setup targets + (for example, `build-images.yml` uses `make setup-release`) keep the inline + setup step. +- **Deploy-specific setup**: infra deploy workflows have additional + steps (`install-collections`, `setup-vault`, `setup_ssh_key`) that + are NOT part of the common setup. The `setup-env` action only + replaces the `make setup-image` step; deploy-specific steps stay + inline. +- **Custom notification**: `security-scan.yml` uses a Mattermost + webhook, not `devx.ci.notify_failure`. The `notify-failure` action + does not apply. + ## Architecture devx is a reusable Python package providing development and CI/CD tools for oblachno-oss projects. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5444b1f..bb0b9a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [Unreleased] + +### Ci + +- Add composite actions (setup-env, notify-failure, quality-checks) in `.gitea/actions/` +- Convert ci.yml, post-merge.yml, build-images.yml to use composite actions + ## [0.50.4] - 2026-08-12 ### Bug Fixes diff --git a/README.md b/README.md index d038d50..7250ef2 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,17 @@ src/devx/ └── molecule/ # Optional molecule testing helpers (for Ansible projects) ``` +### Composite Actions (`.gitea/actions/`) + +Reusable Gitea composite actions for CI workflow steps: + +- `setup-env` — runs `make setup-image` (with optional `EXTRAS=`) +- `notify-failure` — creates a Gitea issue on job failure +- `quality-checks` — 6-step quality gate (lint, tests, speed, docs, translations, security) + +Each consumer repo gets its own copy (no cross-repo references). See +ADR-0003 for the design rationale. + ### Design principles - **Self-contained package** — `src/devx/` never imports from scripts outside the package diff --git a/docs/decisions/0003-composite-actions.md b/docs/decisions/0003-composite-actions.md new file mode 100644 index 0000000..b38c198 --- /dev/null +++ b/docs/decisions/0003-composite-actions.md @@ -0,0 +1,155 @@ +# ADR-0003: Composite Actions for CI Workflow Reuse + +Date: 2026-08-12 +Status: Accepted + +## Context + +The devx repository's Gitea Actions workflows (`.gitea/workflows/ci.yml`, +`post-merge.yml`, `build-images.yml`) repeated multi-step +sequences across jobs: + +1. **Set up environment** — `make setup-image` (optionally with `EXTRAS=`). + Appeared verbatim in 4 jobs across `ci.yml` and `post-merge.yml`, each + with the same `CI_GITEA_API_TOKEN` env wiring. + +2. **Notify on failure** — `python3 -m devx.ci.notify_failure ... --auto-login` + with venv activation, PATH export, and 5 fixed CLI args. Appeared in + 4 jobs (`ci/validate`, `post-merge/detect-and-configure`, + `post-merge/release-and-maintain`, `build-images/build-and-push`), + each differing only in the `--workflow` string. + +3. **Quality checks** — a 6-step sequence (lint-all, pytest-cov, + check-test-speed, devx-docs-check, check-translations, pip-audit) + with venv activation boilerplate on every step. Appeared once in + `ci.yml` validate job, but the same sequence is needed by `grm` and + `infra` (Phase 2b/2c of the cross-repo refactoring plan). + +This duplication had the following costs: + +- **Drift risk**: a fix to notify-failure (for example, new flag, + different env var) had to be applied to 4 places; missing one caused + inconsistent failure notifications. +- **Workflow YAML noise**: the 6-step quality block obscured the + validate job's actual structure (detect-changes, pr-review, + release-dry-run). +- **Cross-repo reuse blocked**: `grm` and `infra` could not adopt the + same quality-checks sequence without copy-pasting the inline steps, + which would amplify the drift problem across 3 repos. +- **Gitea 1.27 constraints**: every `run` step needs explicit `shell:`; + composite actions cannot access `secrets` directly (only `env:`). + These constraints had to be re-discovered and re-applied per step. + +## Decision + +Introduce three Gitea composite actions in `.gitea/actions/`: + +### 1. `setup-env/action.yml` + +Wraps the `make setup-image` call. Single input `extras` (default empty) +forwarded to `make setup-image EXTRAS=`. Reads `CI_GITEA_API_TOKEN` and +`CI_GITEA_USERNAME` from the calling workflow's `env:` context. + +### 2. `notify-failure/action.yml` + +Wraps the `devx.ci.notify_failure` invocation. Single required input +`workflow` (the workflow/job name for the Gitea issue title). Step is +gated by `if: failure()` so it only runs on job failure. Reads +`CI_GITEA_API_TOKEN` from the calling workflow's `env:` context. + +### 3. `quality-checks/action.yml` + +Wraps the 6-step quality sequence. Inputs: + +- `package` (default empty) — sets `DEVX_DOC_VERSIONS_PKG` for doc + version checks (for example, `devx`, `grm`). +- `test-speed-max` (default `15`) — total test seconds threshold. +- `test-speed-max-single` (default `0.5`) — per-test seconds threshold. +- `translations-file` (default empty) — path to `translations.json` + for repos whose translations live outside `src/devx/`. + +Each step activates the venv defensively +(`. .venv/bin/activate 2>/dev/null || true`) so the action works with +both pre-built CI images (which symlink `/opt/venv` to `.venv`) and +fresh `make setup-image` runs. + +### Adoption Scope + +- **`ci.yml` validate job**: `setup-env` + `quality-checks` + + `notify-failure`. +- **`ci.yml` auto-merge job**: `setup-env` only (no quality checks, + no notify-failure — auto-merge failure is surfaced by the validate + job's notify-failure). +- **`post-merge.yml` detect-and-configure**: `setup-env` + + `notify-failure`. +- **`post-merge.yml` release-and-maintain**: `setup-env` (with + `extras: "release"`) + `notify-failure`. +- **`build-images.yml` build-and-push**: `notify-failure` only. The + setup steps use `make setup-release` and `make setup-ci` (not + `make setup-image`), so `setup-env` does not apply. The cleanup job + has no notify-failure step (it only runs on build-and-push success). + +### Same-Repo Copies (No Cross-Repo References) + +Each consumer repo (`devx`, `grm`, `infra`) gets its own copy of the +composite actions under its `.gitea/actions/` directory. There is no +`uses: oblachno-oss/devx/.gitea/actions/...@vX.Y.Z` reference. + +This avoids a single-point-of-failure where a bad `devx` master commit +would break all three repos' CI simultaneously. The cost is three +copies of ~30 lines of YAML each, updated manually when a composite +action changes. Given the stability of these patterns (the inline +versions were unchanged for months), this cost is acceptable. + +## Consequences + +### Positive + +- **Workflow YAML is shorter and clearer**: the validate job's + quality block collapses from 32 lines to 5 lines. The intent + (`uses: ./.gitea/actions/quality-checks`) is more legible than + 6 individually wrapped steps. +- **Drift eliminated**: a change to notify-failure (new flag, different + env var) is applied in one file. All 4 calling sites pick it up. +- **Cross-repo reuse enabled**: Phase 2b (`grm`) and Phase 2c (`infra`) + copy the same `action.yml` files and adopt the same `uses:` pattern. + The quality-checks sequence is now portable. +- **Gitea 1.27 constraints centralized**: the `shell: bash` and + `env:` (not `secrets`) patterns are encoded once per action, not + re-derived per step. +- **No release triggered**: changes to `.gitea/**` are classified as + workflow-only by `devx.ci.classify_changes`. Phase 2a does not + produce a new devx version. `grm`/`infra` bump to the Phase 1 + release (v0.50.4), not a Phase 2a version. + +### Negative + +- **Three copies of each action**: when a composite action changes, + the change must be applied to `devx`, `grm`, and `infra` + independently. This is intentional (see Same-Repo Copies preceding) + but is a maintenance cost. +- **Composite action debugging is harder**: Gitea's log output for + composite action steps is nested under the action name. Finding the + failing step requires reading one more level of indentation. +- **`env:` propagation is implicit**: the calling workflow's top-level + `env:` block must define `CI_GITEA_API_TOKEN` for the composite + action to read it. A workflow that omits this will see an empty + token at runtime, not at lint time. actionlint does not catch this. +- **`quality-checks` is devx-shaped**: the `package` and + `translations-file` inputs exist because consumer repos (for example, + `grm`) have translations files outside the default + `src/devx/translations.json` location and need doc version checks + targeting their own package name. + A repo with a different translations path or package layout would need + a new input or a different action. This is acceptable for the current + 3-repo scope. + +### Neutral + +- **`if: failure()` is preserved**: the `notify-failure` composite + action's step has `if: failure()`, which is evaluated in the + calling workflow's job-status context. This is the standard Gitea + Actions pattern for post-failure notification. +- **Venv activation is defensive**: `. .venv/bin/activate 2>/dev/null + || true` does not fail if the venv is missing (pre-built image path) + or already active. This matches the inline pattern's behavior. diff --git a/docs/tech/architecture.md b/docs/tech/architecture.md index 6a469f2..2df9d49 100644 --- a/docs/tech/architecture.md +++ b/docs/tech/architecture.md @@ -453,6 +453,24 @@ v2 failures. Supports loading custom platforms from a JSON file. - **Secrets via environment** — secrets are passed via environment variables, never on the command line. +## Composite Actions (`.gitea/actions/`) + +Reusable Gitea composite actions eliminate repeated multi-step sequences +across workflows. Each action lives in `.gitea/actions//action.yml` +and is referenced via `uses: ./.gitea/actions/`. + +| Action | Purpose | +|--------|---------| +| `setup-env` | Run `make setup-image` (with optional `EXTRAS=`) | +| `notify-failure` | Create a Gitea issue on job failure via `devx.ci.notify_failure` | +| `quality-checks` | 6-step quality sequence: lint, tests, speed, docs, translations, security | + +Each consumer repo (`devx`, `grm`, `infra`) gets its own copy — there are +no cross-repo composite action references. This avoids a single-point-of-failure +where a bad devx master commit would break all repos' CI simultaneously. + +See ADR-0003 for the full design rationale and Gitea 1.27 constraints. + ## Import rules 1. **`src/devx/` is self-contained** — the package never imports from outside `src/` diff --git a/docs/tech/ci-cd-workflow.md b/docs/tech/ci-cd-workflow.md index 094dbbc..4f3b578 100644 --- a/docs/tech/ci-cd-workflow.md +++ b/docs/tech/ci-cd-workflow.md @@ -38,22 +38,33 @@ The single validation job. Consolidates the former `quality`, `detect-changes`, `release-dry-run`, `pr-review`, and `pre-merge-check` jobs into one job to save checkout+setup overhead. Runs on every PR. -**Quality steps** +**Setup and quality steps** (composite actions) -The main quality gate: +The validate job uses three composite actions from `.gitea/actions/`: -1. **Lint all** — ruff check, ruff format check, pyright, bandit, actionlint - (via `make lint-all`) -2. **Unit tests with 100% coverage** — `make pytest-cov` -3. **Check unit test speed** — `python -m devx.tools.check_test_speed - --max-seconds 4 --max-single-seconds 0.5` -4. **Documentation coverage check** — `python -m devx.ci.doc_coverage - --fail-on-missing` -5. **Translation completeness check** — `python -m devx.ci.check_translations` -6. **Dependency security scan** — `pip-audit --desc --skip-editable` - (best-effort, non-blocking) -7. **Workflow dry-run validation** — `make workflow-dryrun` via act_runner - (best-effort, skipped if act_runner is not installed) +1. **`setup-env`** — runs `make setup-image` to link the pre-built venv + and install the project (no-deps mode) +2. **`quality-checks`** — runs the 6-step quality gate: + - **Lint all** — ruff check, ruff format check, pyright, bandit, + actionlint (via `make lint-all`) + - **Unit tests with 100% coverage** — `make pytest-cov` + - **Check unit test speed** — `python -m devx.tools.check_test_speed + --max-seconds 15 --max-single-seconds 0.5` + - **Documentation gate** — `make devx-docs-check` (coverage + stale + refs + lint + version refs + prose) + - **Translation completeness check** — `python -m devx.ci.check_translations` + - **Dependency security scan** — `pip-audit --desc --skip-editable` + (best-effort, non-blocking) +3. **`notify-failure`** — creates a Gitea issue if any step fails + +The quality-checks action accepts inputs (`package`, `test-speed-max`, +`test-speed-max-single`, `translations-file`) for cross-repo reuse. +See ADR-0003 for the composite action design rationale. + +**Workflow dry-run validation** (inline step, not part of composite action) + +`make workflow-dryrun` via act_runner (best-effort, skipped if +act_runner is not installed). **`detect-changes` step** @@ -574,8 +585,9 @@ picks up the new version number). This prevents infinite loops. ## Failure handling -Every job in the CI and post-merge workflows has a `notify_failure` step -that runs `if: failure()`. This creates a Gitea issue with the workflow name, -run ID, and commit SHA, ensuring failures that would otherwise go unnoticed -in the Actions tab are surfaced as issues. The issue is created via the tea -CLI with a `bug` label if available. +Every job in the CI, post-merge, and build-images workflows uses the +`notify-failure` composite action (`.gitea/actions/notify-failure`), +which runs `if: failure()`. This creates a Gitea issue with the workflow +name, run ID, and commit SHA, ensuring failures that would otherwise go +unnoticed in the Actions tab are surfaced as issues. The issue is created +via the tea CLI with a `bug` label if available.