Public Access
156 lines
7.0 KiB
Markdown
156 lines
7.0 KiB
Markdown
# 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.
|