This document describes the optimized CI/CD pipeline architecture. It covers the original cleanup wave (issues #700, #701, #703, #714) as well as the follow-up hardening wave (issues #1136, #1137, #1138, #1139).
All reusable logic lives under .github/actions/:
| Action | Purpose |
|---|---|
setup-rust |
Install Rust toolchain + system deps + Swatinem cache + optional cargo tools (with retry) |
setup-kind-cluster |
Provision kind cluster, load image, install CRDs, deploy operator |
collect-e2e-logs |
Dump operator logs, K8s events, StellarNode status → artifact |
collect-failure-diagnostics |
Unified failing-run diagnostics bundle (issue #1151) |
setup-perf-env |
Install k6/kind/kubectl, create cluster, deploy operator with RBAC, port-forward |
build-operator |
Build Rust binary + Docker image + artifact upload in one call (issue #1136) |
See docs/ci-failure-diagnostics.md for the
bundle layout and how to invoke scripts/ci/collect-failure-diagnostics.sh
locally.
Several workflows still re-implemented the same Rust bootstrap after
setup-rust already covered it:
- Double
cargo install—ci.yml,dependency-review.yml, andmaintenance.ymlpassedextra-toolstosetup-rustand then ran a second install loop for the same crates. - Raw toolchain install —
security-audit.ymlanddead-code-report.ymlstill inlineddtolnay/rust-toolchain+Swatinem/rust-cacheinstead of callingsetup-rust. - Leftover duplicate in
stale-docs.yml— after #1136 it calledsetup-rustand still installed the toolchain again viadtolnay.
Fix:
setup-rustnow owns cargo-tool install with a 3-attempt retry.- Workflow jobs only pass
extra-tools:— no per-job install steps. - Scheduled/security/dead-code workflows delegate to
setup-rust. ci-reliability-testasserts retry lives in the composite and that workflows do not re-bootstrapcargo-audit/cargo-tarpaulin/cargo-deny.
Verification:
# Only release.yml (cross-compile matrix) may call rust-toolchain directly
grep -RIn 'dtolnay/rust-toolchain' .github/ \
| grep -v 'setup-rust/action.yml'
# No duplicated cargo-tool bootstrap in workflows
grep -RIn -E 'cargo install (cargo-audit|cargo-tarpaulin|cargo-deny)' \
.github/workflows/ || echo "none"
bash scripts/ci/check-cache-keys.shThe chaos-tests, soak-test, performance, and verify-operator-boot
workflows previously each contained their own copy of:
setup-rust → cargo build --release → docker build → docker save → upload-artifact
This is now consolidated into .github/actions/build-operator/action.yml.
Each workflow calls the composite action with the appropriate image-tag,
cache-key, and optional binary-only / upload-artifact flags.
Additionally, stale-docs.yml previously had its own dtolnay/rust-toolchain
install + manual actions/cache block. It now uses setup-rust for
consistency.
Verification: grep for dtolnay/rust-toolchain outside of
.github/actions/setup-rust/action.yml — the only remaining hit should be
release.yml (cross-compilation matrix targets require direct toolchain
installation per platform).
Missing Makefile targets that were declared in .PHONY but had no recipe
body:
| Target | Fix |
|---|---|
docker-multiarch |
Added recipe that dispatches the multiarch-build.yml workflow via gh workflow run |
run |
Added recipe as a documented alias for run-local (matches README references) |
update-doc-baseline |
New target to run doc-check --update-baseline |
docs-check-strict |
New target that runs doc-check status without --warn-only (hard fail) |
docs-lint |
New target that runs cargo doc with RUSTDOCFLAGS="-D warnings" |
sort-manifests |
New target that invokes scripts/sort-manifests.py on stdin |
Verification: make help — all targets declared in .PHONY now have a
corresponding recipe and description.
Two changes enforce a zero-tolerance warning policy:
-
ci.ymllint job — a new "Check rustdoc (warnings as errors)" step runsRUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace …immediately after the existing clippy steps. A missing or malformed doc comment now fails CI. -
docs-deploy.yml— removed thecontinue-on-error: trueguard on thecargo docstep and addedRUSTDOCFLAGS="-D warnings". Broken docs can no longer silently pass and be published. -
Makefile
ci-local—docs-lintis now part of the local CI pipeline so contributors catch doc warnings before pushing.
Verification:
# Local check
make docs-lint
# Simulate CI
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace \
--features "rest-api,metrics,admission-webhook,k8s-v1-30"Two sources of non-determinism have been addressed:
-
crd-genMakefile target — output ofcargo run --bin crdgenis now piped throughscripts/sort-manifests.py, which sorts all YAML mapping keys recursively and orders documents by(kind, namespace, name). -
bundle-renderMakefile target — output ofhelm templateis sorted the same way before being written torendered/manifests.yaml. -
ci.ymlmanifest-orderjob — new CI job that:- Verifies
config/crd/stellarnode-crd.yamlis already in canonical sorted order (fails if uncommitted CRD changes are present without sorting). - Renders the Helm chart twice and diffs the sorted outputs to confirm idempotence.
- Verifies
Verification:
# Sort an existing manifest and check for diffs
python3 scripts/sort-manifests.py config/crd/stellarnode-crd.yaml \
| diff - config/crd/stellarnode-crd.yaml && echo "Already sorted"
# Regenerate CRD with deterministic output
make crd-gen
git diff config/crd/stellarnode-crd.yaml # should be empty if already sorted- Change detection gates expensive jobs (helm-lint, api-docs, examples-smoke-test, security-audit) so they only run when relevant files change.
- Unified Rust cache via
setup-rustcomposite action with per-jobshared-key. - Removed duplicate system-dependency install blocks (now in
setup-rust). - Removed duplicate
actions/checkout@v6references (standardised on@v4). lintandsecurity-auditrun in parallel (both depend only onchanges).testruns on every PR;coverageruns on main pushes only (tarpaulin is slow).- Removed standalone
pre-commit.ymlandcommit-lint.ymlworkflows — lint/format is covered by the mainci.ymllintjob.
Parallel lint + audit + test/coverage, combined with shared caching, reduces the critical path by ~35–40% compared to the previous sequential layout.
- Extracted cluster provisioning into
setup-kind-clustercomposite action. - Parallel execution: experiments 01–02 (pod-kill, network partition) run in
chaos-kill-networkjob; experiments 03–05 (latency, peer-partition, disk-fill) run inchaos-latency-diskjob simultaneously. - Consolidated logging via
collect-e2e-logscomposite action. - Binary built once in a
buildjob and downloaded as an artifact by both parallel jobs — no duplicate Rust compilation.
- Uses
setup-kind-clusterfor cluster provisioning. - Uses
collect-e2e-logsfor failure-time log collection. - Removed duplicated Rust toolchain + apt-get blocks.
- Uses
setup-rustcomposite action. - Runs on main pushes and
workflow_dispatchonly (kind-cluster boot check is too heavy for every contributor PR). - Artifact name includes
github.run_idto avoid collisions.
- Replaces the former
benchmark.yml,performance-regression.yml, andwebhook-benchmark.ymlwith a single matrix-driven workflow. - Runs on main pushes (path-filtered) and
workflow_dispatch— not on PRs. - Shared build job produces the operator binary and Docker image once; all three suites (operator, regression, webhook) download the same artifact.
- Matrix execution runs operator and regression suites via
setup-perf-env, and the webhook suite directly (no kind cluster required). - Shared baseline comparison via
.github/actions/compare-benchmarkscomposite action wrappingcompare_benchmarks.py.
- Runs on main pushes (path-filtered) and
workflow_dispatch— not on PRs. - Per-platform GHA cache scopes (
multiarch-amd64,multiarch-arm64) prevent cross-arch cache pollution and improve cache hit rates. arch-benchmarkjobs usesetup-rustcomposite action.- Combined manifest build pulls from both per-platform caches.
- Eliminated duplicate Docker build:
containerjob first attempts to re-tag thesha-<sha>image already published bymultiarch-build.yml. A fresh build only runs as a fallback when the sha image is unavailable. - Fail-safe:
validatejob enforces semver format AND Cargo.toml version match before any build or publish step runs. A mismatch is now a hard error (previously a warning). releasejob depends on ALL of:build-artifacts,container,security,helm— broken builds can never be tagged for release.- Standardised on
actions/upload-artifact@v4/actions/download-artifact@v4.
All workflows now use consistent, security-hardened action versions:
| Action | Version | Security Notes |
|---|---|---|
actions/checkout |
@v7 |
Latest with security patches |
actions/setup-node |
@v4 |
Stable, consistent |
actions/setup-python |
@v6 |
Fixed inconsistency (was mixed v5/v6) |
actions/upload-artifact |
@v4 |
Consistent across all workflows |
actions/download-artifact |
@v4 |
Consistent across all workflows |
actions/cache |
@v4 |
Stable caching |
helm/kind-action |
v1.14.0 |
Pinned for stability |
docker/build-push-action |
@v7 |
Latest with security improvements |
aquasecurity/trivy-action |
@v0.36.0 |
Fixed inconsistency (was mixed v0.35.0/v0.36.0) |
Swatinem/rust-cache |
@v2 |
Optimized configuration |
- Valid base image digest: Fixed dummy SHA256 → actual
debian:bookworm-slimdigest - Supply chain verification: Ensures reproducible, verified builds
- SBOM generation: Enabled for all release artifacts
- Provenance attestation: Cryptographic build provenance for containers
- Centralized audit config: Moved from inline CLI ignores to documented
.cargo/audit.toml - Justified ignores: Each security advisory ignore includes:
- Technical rationale for why it's safe to ignore
- Conditions for removal
- Review date for re-evaluation
- Eliminated phantom entries: Removed non-existent future-year RUSTSEC IDs
Validates pipeline stability and hardening:
- ✅ Docker config validation: Verifies base image digests are valid
- ✅ Security audit testing: Confirms audit configuration is functional
- ✅ Action version consistency: Detects version drift across workflows
- ✅ Cache configuration: Validates deprecated settings are removed
- ✅ Retry logic testing: Confirms error handling patterns exist
- ✅ Documentation completeness: Ensures troubleshooting guides exist
New comprehensive guide: .github/CI_TROUBLESHOOTING.md
Covers common failure scenarios:
- Docker build failures and digest issues
- Security audit failures and ignore management
- Test timeouts and performance regressions
- Cache restoration problems
- Action version conflicts
Includes local reproduction steps:
# Reproduce CI failures locally
docker build --target runtime --platform linux/amd64 .
cargo test --all-features --workspace
cargo audit # Uses .cargo/audit.toml config- Primary CI gate:
repo-wide-link-check(lychee) inci.yml. - Removed overlapping PR jobs:
markdown-link-checkanddocs-link-check. - Local/checklist:
python3 scripts/check-links.py(viamake health) still works. - Scheduled link rot: standalone
.github/workflows/link-check.yml.
- Canonical PR gate: Python
crd_migration_lintinquickstart-validation.yml(scripts/crd_migration_lint.py --against origin/mainplusscripts/tests/test_crd_migration_lint.py). - Local/ad-hoc only:
scripts/check-crd-compatibility.sh(no longer aci.ymljob).
- PR/push path:
ci.ymlsecurity-audit(runs when dependency files change). - Schedule / SBOM / cargo-deny / scorecard:
.github/workflows/security-audit.yml(schedule +workflow_dispatchonly — no duplicate PR trigger). - Not duplicated in:
dependency-review.ymlormaintenance.yml.
- Canonical workflow:
.github/workflows/security-scan.yml(push tomain, schedule,workflow_dispatch). Uses.github/actions/security-scanfor image scans. - CI image scan after publish:
ci.ymlsecurity-scanjob (same composite action).
- Unique job only:
maintenance.yml→ stale-artifact regression tests. - Scheduled cargo-audit / docs link checks live in
security-audit.yml/link-check.yml.
- Single maintenance/chore template:
.github/ISSUE_TEMPLATE/maintenance.yml(covers dependency updates, CI hygiene, docs, refactors).
release.ymlvalidateowns semver + Cargo.toml matching; helm job owns helm lint.release-gate.yml/scripts/release-gate.shkeep unique value only: CHANGELOG entry + helm unittest.
- Success rate: >95% on main branch
- Build duration: <45 minutes end-to-end
- Cache hit rate: >80% for Rust builds
- Security audit: 0 unaddressed critical/high CVEs
- 3+ consecutive main branch failures
- Individual job runtime >60 minutes
- Cache hit rate <60% (indicates configuration issues)
- New high/critical CVEs not addressed within 7 days