Files
pezkuwi-sdk/.gitlab-ci.yml
T
Kurdistan Tech MinistryandClaude Sonnet 4.6 73e5bfea3e fix(ci): retry cargo-deny-advisories on transient network failure
advisory-db is cloned from GitHub (rustsec/advisory-db). The runner
(merkez-server) occasionally fails to reach GitHub — the clone fails
silently, the advisory-dbs/ directory is not created, and cargo-deny
then tries to run git-log on a non-existent path, producing:
  'fatal: cannot change to ...' parsed as an ISO-8601 date → crash

Real fix: GitLab native retry (max: 2, when: script_failure).
This retries the full job on infrastructure failures without bypassing
the security check — if all 3 attempts fail, the job still fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 22:10:40 +03:00

587 lines
21 KiB
YAML

# ============================================================================
# Pezkuwi SDK — GitLab CI/CD Pipeline
# Mirrors: checks-quick, checks, security-audit, tests, docs, release
#
# Same unified image as GitHub Actions — no apt-get overhead, sccache ready.
# ============================================================================
image: ghcr.io/pezkuwichain/ci-unified:bullseye-1.88.0-2025-06-27-v202511141243
stages:
- quick-checks
- checks
- security
- test
- docs
- build
- release
# ── Variables ─────────────────────────────────────────────────────────────────
variables:
CARGO_HOME: "$CI_PROJECT_DIR/.cargo"
CARGO_INCREMENTAL: "0"
CARGO_TERM_COLOR: "always"
RUST_BACKTRACE: "short"
RUSTFLAGS: "-D warnings"
# Disable forklift (GitHub-only sccache proxy not available on GitLab)
RUSTC_WRAPPER: ""
# Faster artifact transfer
FF_USE_FASTZIP: "true"
ARTIFACT_COMPRESSION_LEVEL: "fast"
CACHE_COMPRESSION_LEVEL: "fast"
# ── Workflow: avoid duplicate pipelines ───────────────────────────────────────
workflow:
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "web"
# ── Default ───────────────────────────────────────────────────────────────────
default:
before_script:
- rustup show
- rustc --version && cargo --version
- rustup target add wasm32v1-none
cache:
key:
files:
- Cargo.lock
- rust-toolchain.toml
paths:
- .cargo/registry/
- .cargo/git/
policy: pull-push
interruptible: true
# ╔══════════════════════════════════════════════════════════════════════════════
# QUICK CHECKS — fast feedback on every push/MR (mirrors checks-quick.yml)
# ╚══════════════════════════════════════════════════════════════════════════════
fmt:
stage: quick-checks
script:
- cargo fmt --all -- --check
check-toml-format:
stage: quick-checks
variables:
CARGO_HOME: /root/.cargo
script:
- taplo format --check --config .config/taplo.toml
- echo "Run 'taplo format --config .config/taplo.toml' to fix TOML issues"
check-dependency-rules:
stage: quick-checks
script:
- cd bizinikiwi/ && ../.gitlab/ensure-deps.sh
check-fail-ci:
stage: quick-checks
script:
- |
if rg --line-number --hidden --type rust --glob '!{.git,target}' "FAIL-CI" .; then
echo "FAIL-CI marker found — remove before merging"
exit 1
fi
echo "✅ No FAIL-CI markers found"
check-workspace-integrity:
stage: quick-checks
variables:
CARGO_HOME: /root/.cargo
script:
- pip3 install toml "cargo-workspace>=1.2.6" --quiet
- |
python3 .github/scripts/check-workspace.py . \
--exclude "bizinikiwi/pezframe/contracts/fixtures/build" "bizinikiwi/pezframe/contracts/fixtures/contracts/common"
- python3 .github/scripts/deny-git-deps.py .
check-zepter:
stage: quick-checks
script:
- cargo metadata --format-version=1 --locked > /dev/null
- cargo install zepter@1.82.1 --force
- export PATH="$CARGO_HOME/bin:$PATH"
- zepter --version
- zepter run check
test-rust-features:
stage: quick-checks
script:
- bash .gitlab/rust-features.sh .
check-umbrella:
stage: quick-checks
script:
- pip3 install "cargo-workspace>=1.2.4" toml --quiet
- cargo install zepter@1.82.1 --force --quiet
- export PATH="$CARGO_HOME/bin:$PATH"
- git config --global --add safe.directory '*'
- |
UMBRELLA_VERSION=$(cargo metadata --format-version=1 | \
python3 -c "import json,sys; pkgs=json.load(sys.stdin)['packages']; \
print(next(p['version'] for p in pkgs if p['manifest_path'].endswith('umbrella/Cargo.toml')))")
echo "Umbrella version: $UMBRELLA_VERSION"
python3 scripts/generate-umbrella.py --sdk . --version "$UMBRELLA_VERSION"
# 'default' workflow runs propagate-feature --fix for workspace + umbrella.
# check-zepter already validates workspace is clean, so this is a no-op
# for workspace crates — only umbrella feature lists get populated.
zepter run default
cargo +nightly fmt -p pezkuwi-sdk
- |
if ! git diff --quiet; then
echo "❌ Umbrella crate is out of sync. Run: python3 scripts/generate-umbrella.py --sdk . --version <VERSION> && zepter run default"
git diff
exit 1
fi
echo "✅ Umbrella crate is up to date"
check-licenses-headers:
stage: quick-checks
script:
- |
FAIL=0
for dir in pezkuwi pezcumulus bizinikiwi; do
echo "Checking license headers in ./$dir..."
MISSING=""
while IFS= read -r f; do
head -20 "$f" | grep -qiE "(apache|gpl|mit|unlicense|copyright)" || MISSING="$MISSING\n$f"
done < <(find "./$dir" -name "*.rs" -type f | head -200)
if [ -n "$MISSING" ]; then
echo "⚠️ Files potentially missing license headers in $dir:"
printf "$MISSING\n" | head -20
FAIL=1
else
echo "✅ $dir: license headers OK (sampled)"
fi
done
exit $FAIL
# ╔══════════════════════════════════════════════════════════════════════════════
# CHECKS — deeper linting and feature checks (mirrors checks.yml)
# ╚══════════════════════════════════════════════════════════════════════════════
clippy:
stage: checks
needs: [fmt]
variables:
SKIP_WASM_BUILD: "1"
timeout: 2h
script:
- git config --global --add safe.directory '*'
- |
cargo clippy --all-targets --all-features --locked --workspace --quiet \
--exclude pezpallet-revive-eth-rpc \
--exclude pezkuwi-zombienet-sdk-tests \
-- -D warnings
check-try-runtime:
stage: checks
needs: [clippy]
variables:
SKIP_WASM_BUILD: "1"
timeout: 120m
script:
- git config --global --add safe.directory '*'
# try-runtime,experimental is a superset — covers the separate try-runtime and
# teyrchain-template-node checks (template node is in the workspace under
# templates/teyrchain/node, so --all already includes it)
- cargo check --locked --all --features try-runtime,experimental --quiet
check-core-crypto-features:
stage: checks
needs: [fmt]
script:
- rustup target add wasm32v1-none
- |
for pkg in core application-crypto keyring; do
echo "--- Checking bizinikiwi/primitives/$pkg ---"
(cd bizinikiwi/primitives/$pkg && ./check-features-variants.sh)
done
# ╔══════════════════════════════════════════════════════════════════════════════
# SECURITY — mirrors security-audit.yml (runs on push + schedule weekly Mon 06:00)
# ╚══════════════════════════════════════════════════════════════════════════════
cargo-deny-advisories:
stage: security
needs: []
before_script: []
# advisory-db clone from GitHub can fail transiently (network, rate limit).
# Retry up to 2 times before marking the job as failed.
retry:
max: 2
when: script_failure
script:
- cargo install cargo-deny --locked -q
- cargo deny check advisories --config deny.toml
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_MERGE_REQUEST_IID
- if: $CI_PIPELINE_SOURCE == "schedule"
cargo-deny-licenses:
stage: security
needs: []
before_script: []
script:
- cargo install cargo-deny --locked -q
- cargo deny check licenses --config deny.toml
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_MERGE_REQUEST_IID
- if: $CI_PIPELINE_SOURCE == "schedule"
cargo-deny-sources:
stage: security
needs: []
before_script: []
script:
- cargo install cargo-deny --locked -q
- cargo deny check sources --config deny.toml
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_MERGE_REQUEST_IID
- if: $CI_PIPELINE_SOURCE == "schedule"
cargo-audit:
stage: security
needs: []
before_script: []
script:
- cargo install cargo-audit --locked -q
- cargo audit --json 2>/dev/null > /tmp/audit.json || true
- python3 .gitlab/check-audit.py < /tmp/audit.json
allow_failure: false
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "schedule"
# ╔══════════════════════════════════════════════════════════════════════════════
# TEST — mirrors tests-linux-stable.yml (manual-only on GitHub, schedule here)
# ╚══════════════════════════════════════════════════════════════════════════════
# Tests split into 3 partitions via cargo-nextest — mirrors GitHub matrix strategy
.test-base:
stage: test
tags: [pezkuwi-runner]
needs: [clippy, check-try-runtime]
variables:
CARGO_HOME: /root/.cargo
RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings"
WASM_BUILD_NO_COLOR: "1"
CARGO_INCREMENTAL: "0"
# Skip WASM runtime builds — unit/integration tests use mock environments
# and don't need actual WASM blobs. This significantly reduces compile time.
SKIP_WASM_BUILD: "1"
# When CARGO_TARGET_DIR is outside the project dir, wasm-builder's build
# script can't find Cargo.lock and resolves deps fresh (without --locked),
# picking up versions that require newer Rust. Point it to the workspace root.
WASM_BUILD_WORKSPACE_HINT: "$CI_PROJECT_DIR"
before_script:
- rustup show
- rustc --version && cargo --version
- rustup target add wasm32v1-none
- export CACHE_KEY="$(cat Cargo.lock rust-toolchain.toml | md5sum | cut -c1-12)"
- export CARGO_TARGET_DIR="/cache/cargo-target/${CACHE_KEY}/${CI_JOB_NAME}"
- mkdir -p "$CARGO_TARGET_DIR"
- find /cache/cargo-target -mindepth 1 -maxdepth 1 -type d ! -name "${CACHE_KEY}" -exec rm -rf {} + 2>/dev/null || true
- echo "CARGO_TARGET_DIR=$CARGO_TARGET_DIR (disk $(df -h /cache/cargo-target | tail -1 | awk '{print $3 "/" $2 " used, " $4 " free"}'))"
cache: []
timeout: 150m
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "web"
when: manual
test-partition-1:
extends: .test-base
script:
- git config --global --add safe.directory '*'
- |
cargo nextest run \
--workspace --locked --release --no-fail-fast --cargo-quiet \
--features try-runtime,experimental,ci-only-tests \
--partition count:1/3
artifacts:
when: always
paths: [test-results-1.xml]
reports:
junit: test-results-1.xml
expire_in: 7 days
test-partition-2:
extends: .test-base
script:
- git config --global --add safe.directory '*'
- |
cargo nextest run \
--workspace --locked --release --no-fail-fast --cargo-quiet \
--features try-runtime,experimental,ci-only-tests \
--partition count:2/3
artifacts:
when: always
paths: [test-results-2.xml]
reports:
junit: test-results-2.xml
expire_in: 7 days
test-partition-3:
extends: .test-base
script:
- git config --global --add safe.directory '*'
- |
cargo nextest run \
--workspace --locked --release --no-fail-fast --cargo-quiet \
--features try-runtime,experimental,ci-only-tests \
--partition count:3/3
# First partition also runs runtime-api tests
- cargo nextest run -p pezsp-api-test --features enable-pez-staging-api --cargo-quiet
artifacts:
when: always
paths: [test-results-3.xml]
reports:
junit: test-results-3.xml
expire_in: 7 days
test-no-try-runtime-1:
extends: .test-base
script:
- git config --global --add safe.directory '*'
- |
cargo nextest run \
--workspace --locked --release --no-fail-fast --cargo-quiet \
--features experimental,ci-only-tests \
--partition count:1/2
test-no-try-runtime-2:
extends: .test-base
script:
- git config --global --add safe.directory '*'
- |
cargo nextest run \
--workspace --locked --release --no-fail-fast --cargo-quiet \
--features experimental,ci-only-tests \
--partition count:2/2
wasm-check:
stage: test
tags: [pezkuwi-runner]
needs: [check-try-runtime]
variables:
CARGO_HOME: /root/.cargo
before_script:
- rustup show
- rustc --version && cargo --version
- rustup target add wasm32v1-none
- export CACHE_KEY="$(cat Cargo.lock rust-toolchain.toml | md5sum | cut -c1-12)"
- export CARGO_TARGET_DIR="/cache/cargo-target/${CACHE_KEY}/${CI_JOB_NAME}"
- mkdir -p "$CARGO_TARGET_DIR"
- find /cache/cargo-target -mindepth 1 -maxdepth 1 -type d ! -name "${CACHE_KEY}" -exec rm -rf {} + 2>/dev/null || true
- echo "CARGO_TARGET_DIR=$CARGO_TARGET_DIR"
cache: []
script:
- cargo check -p pezkuwichain-runtime --locked --target wasm32v1-none
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_MERGE_REQUEST_IID
# Runtime migration — disabled until try-runtime Zagros endpoints are live
# Enable by removing the `when: manual` rule and setting proper URIs
runtime-migration:
stage: test
needs: []
variables:
RUSTFLAGS: ""
script:
- |
echo "Runtime migration check — currently manual/disabled"
echo "Targets: zagros, asset-hub-zagros, collectives-zagros, coretime-zagros"
echo "Re-enable when wss://try-runtime-zagros.pezkuwichain.io is live"
rules:
- if: $CI_PIPELINE_SOURCE == "web"
when: manual
- if: $CI_PIPELINE_SOURCE == "schedule"
when: manual
allow_failure: true
# ╔══════════════════════════════════════════════════════════════════════════════
# DOCS — mirrors docs.yml
# ╚══════════════════════════════════════════════════════════════════════════════
test-doc:
stage: docs
tags: [pezkuwi-runner]
needs: [clippy]
variables:
CARGO_HOME: /root/.cargo
RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings"
SKIP_WASM_BUILD: "0"
before_script:
- rustup show
- rustc --version && cargo --version
- rustup target add wasm32v1-none
- export CACHE_KEY="$(cat Cargo.lock rust-toolchain.toml | md5sum | cut -c1-12)"
- export CARGO_TARGET_DIR="/cache/cargo-target/${CACHE_KEY}/${CI_JOB_NAME}"
- mkdir -p "$CARGO_TARGET_DIR"
- find /cache/cargo-target -mindepth 1 -maxdepth 1 -type d ! -name "${CACHE_KEY}" -exec rm -rf {} + 2>/dev/null || true
- echo "CARGO_TARGET_DIR=$CARGO_TARGET_DIR"
cache: []
timeout: 5h
script:
- git config --global --add safe.directory '*'
- |
cargo test --doc --workspace --locked --all-features \
--exclude pezkuwi-zombienet-sdk-tests \
--exclude pezpallet-revive-eth-rpc
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "schedule"
allow_failure: true
build-rustdoc:
stage: docs
needs: [test-doc]
variables:
SKIP_WASM_BUILD: "1"
RUSTDOCFLAGS: >-
-Dwarnings
--default-theme=ayu
--html-in-header ./docs/sdk/assets/header.html
--extend-css ./docs/sdk/assets/theme.css
--html-after-content ./docs/sdk/assets/after-content.html
timeout: 4h
script:
- git config --global --add safe.directory '*'
- |
cargo doc --all-features --workspace --no-deps --locked \
--exclude pezkuwi-zombienet-sdk-tests \
--exclude pezpallet-revive-eth-rpc
- rm -f target/doc/.lock
- mv target/doc ./crate-docs
- echo '<meta http-equiv=refresh content=0;url=pezkuwi_sdk_docs/index.html>' > ./crate-docs/index.html
- tar czf crate-docs.tar.gz crate-docs/
artifacts:
paths: [crate-docs.tar.gz]
expire_in: 1 day
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
allow_failure: true
build-implementers-guide:
stage: docs
needs: []
before_script: []
script:
- mdbook build ./pezkuwi/roadmap/implementers-guide
artifacts:
paths: [pezkuwi/roadmap/implementers-guide/book/]
expire_in: 1 day
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_MERGE_REQUEST_IID
allow_failure: true
# GitLab Pages — publish rustdocs to pages.gitlab.io
pages:
stage: docs
needs: [build-rustdoc, build-implementers-guide]
before_script: []
script:
- mkdir -p public
- tar xzf crate-docs.tar.gz -C public/ --strip-components=1 || true
- cp -r pezkuwi/roadmap/implementers-guide/book public/guide || true
artifacts:
paths: [public/]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
allow_failure: true
# ╔══════════════════════════════════════════════════════════════════════════════
# BUILD — release binary (main branch + tags)
# ╚══════════════════════════════════════════════════════════════════════════════
build-release:
stage: build
needs: [clippy, wasm-check]
variables:
RUSTFLAGS: "-D warnings"
timeout: 4h
script:
- git config --global --add safe.directory '*'
- cargo build --release -p pezkuwi --locked
- |
echo "=== Build artifacts ==="
ls -lh target/release/pezkuwi \
target/release/pezkuwi-execute-worker \
target/release/pezkuwi-prepare-worker
echo ""
echo "=== SHA256 checksums ==="
sha256sum \
target/release/pezkuwi \
target/release/pezkuwi-execute-worker \
target/release/pezkuwi-prepare-worker | tee SHA256SUMS
artifacts:
name: "pezkuwi-$CI_COMMIT_REF_NAME"
paths:
- target/release/pezkuwi
- target/release/pezkuwi-execute-worker
- target/release/pezkuwi-prepare-worker
- SHA256SUMS
expire_in: 7 days
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
# ╔══════════════════════════════════════════════════════════════════════════════
# RELEASE — GitLab Release with assets (tags only)
# ╚══════════════════════════════════════════════════════════════════════════════
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
before_script: []
needs: [build-release]
script:
- echo "Creating GitLab Release for $CI_COMMIT_TAG"
release:
tag_name: "$CI_COMMIT_TAG"
name: "Pezkuwi SDK $CI_COMMIT_TAG"
description: |
## Pezkuwi SDK $CI_COMMIT_TAG
### Binaries (Linux x86_64)
| Binary | Description |
|--------|-------------|
| `pezkuwi` | Node binary |
| `pezkuwi-execute-worker` | WASM execution worker |
| `pezkuwi-prepare-worker` | WASM preparation worker |
SHA256 checksums in `SHA256SUMS`.
### Verify
```bash
sha256sum -c SHA256SUMS
```
assets:
links:
- name: "pezkuwi (node binary)"
url: "$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/target/release/pezkuwi?job=build-release"
link_type: package
- name: "pezkuwi-execute-worker"
url: "$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/target/release/pezkuwi-execute-worker?job=build-release"
link_type: package
- name: "pezkuwi-prepare-worker"
url: "$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/target/release/pezkuwi-prepare-worker?job=build-release"
link_type: package
- name: "SHA256SUMS"
url: "$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/SHA256SUMS?job=build-release"
link_type: other
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/