mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 05:40:31 +00:00
* chore: add workspace clippy lints, release profiles, COMPAT.md, diff-registry.sh - Workspace clippy lints: or_fun_call, redundant_clone, collection_is_never_read, naive_bytecount, stable_sort_primitive, large_types_passed_by_value, assigning_clones - Fix or_fun_call in cargo_registry.rs (unwrap_or -> unwrap_or_else) - Release profiles: release (thin LTO) + release-official (full LTO, codegen-units=1) - COMPAT.md: protocol compatibility matrix for all 7 registries (40 endpoints) - scripts/diff-registry.sh: differential smoke tests (Docker/npm/Cargo/PyPI/Go/Raw) * ci: add typos spell-check job and config * chore: add .git-blame-ignore-revs for bulk fmt/clippy commits * chore: unify PR template with What/Why/Checklist format * docs: add new registry checklist and improve contributing guide * fix: correct typos action SHA to v1.45.0
257 lines
9.6 KiB
YAML
257 lines
9.6 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
permissions: read-all
|
|
|
|
jobs:
|
|
typos:
|
|
name: Typos
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
- uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0
|
|
|
|
test:
|
|
name: Test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
|
|
|
|
- name: Check formatting
|
|
run: cargo fmt --check
|
|
|
|
- name: Clippy
|
|
run: cargo clippy --package nora-registry -- -D warnings
|
|
|
|
- name: Run tests
|
|
run: cargo test --package nora-registry
|
|
|
|
|
|
lint-workflows:
|
|
name: Lint Workflows
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
- name: Install actionlint
|
|
run: |
|
|
ACTIONLINT_VERSION=1.7.12
|
|
curl -sLO "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
|
|
tar xzf "actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" actionlint
|
|
- name: Run actionlint
|
|
run: ./actionlint -ignore "shellcheck reported issue" -ignore "SC[0-9]"
|
|
|
|
|
|
coverage:
|
|
name: Coverage
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
|
|
|
|
- name: Install tarpaulin
|
|
run: cargo install cargo-tarpaulin --locked
|
|
|
|
- name: Run coverage
|
|
run: |
|
|
cargo tarpaulin --config tarpaulin.toml 2>&1 | tee /tmp/tarpaulin.log
|
|
COVERAGE=$(python3 -c "import json; d=json.load(open('coverage/tarpaulin-report.json')); print(f\"{d['coverage']:.1f}\")")
|
|
echo "COVERAGE=$COVERAGE" >> $GITHUB_ENV
|
|
echo "Coverage: $COVERAGE%"
|
|
|
|
- name: Update coverage badge
|
|
uses: schneegans/dynamic-badges-action@0e50b8bad39e7e1afd3e4e9c2b7dd145fad07501 # v1.8.0
|
|
with:
|
|
auth: ${{ secrets.GIST_TOKEN }}
|
|
gistID: ${{ vars.COVERAGE_GIST_ID }}
|
|
filename: nora-coverage.json
|
|
label: coverage
|
|
message: ${{ env.COVERAGE }}%
|
|
valColorRange: ${{ env.COVERAGE }}
|
|
minColorRange: 0
|
|
maxColorRange: 100
|
|
|
|
security:
|
|
name: Security
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
security-events: write
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
|
|
|
|
# ── Secrets ────────────────────────────────────────────────────────────
|
|
- name: Gitleaks — scan for hardcoded secrets
|
|
run: |
|
|
curl -sL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \
|
|
| tar xz -C /usr/local/bin gitleaks
|
|
gitleaks detect --source . --config .gitleaks.toml --exit-code 1 --report-format sarif --report-path gitleaks.sarif
|
|
|
|
# ── CVE in Rust dependencies ────────────────────────────────────────────
|
|
- name: Install cargo-audit
|
|
run: cargo install cargo-audit --locked
|
|
|
|
- name: cargo audit — RustSec advisory database
|
|
run: |
|
|
cargo audit --ignore RUSTSEC-2025-0119
|
|
cargo audit --ignore RUSTSEC-2025-0119 --json > /tmp/audit.json || true
|
|
|
|
- name: Upload cargo-audit results as SARIF
|
|
if: always()
|
|
run: |
|
|
python3 -c "
|
|
import json, sys
|
|
sarif = {
|
|
'version': '2.1.0',
|
|
'\$schema': 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json',
|
|
'runs': [{'tool': {'driver': {'name': 'cargo-audit', 'version': '0.21', 'informationUri': 'https://github.com/rustsec/rustsec'}}, 'results': []}]
|
|
}
|
|
with open('cargo-audit.sarif', 'w') as f:
|
|
json.dump(sarif, f)
|
|
"
|
|
|
|
- name: Upload SAST results to GitHub Security tab
|
|
uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4
|
|
if: always()
|
|
with:
|
|
sarif_file: cargo-audit.sarif
|
|
category: cargo-audit
|
|
|
|
# ── Licenses, banned crates, supply chain policy ────────────────────────
|
|
- name: cargo deny — licenses and banned crates
|
|
uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2
|
|
with:
|
|
command: check
|
|
arguments: --all-features
|
|
|
|
# ── CVE scan of source tree and Cargo.lock ──────────────────────────────
|
|
- name: Trivy — filesystem scan (Cargo.lock + source)
|
|
if: always()
|
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
|
with:
|
|
scan-type: fs
|
|
scan-ref: .
|
|
format: sarif
|
|
output: trivy-fs.sarif
|
|
severity: HIGH,CRITICAL
|
|
exit-code: 1
|
|
|
|
- name: Upload Trivy fs results to GitHub Security tab
|
|
uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4
|
|
if: always()
|
|
with:
|
|
sarif_file: trivy-fs.sarif
|
|
category: trivy-fs
|
|
|
|
integration:
|
|
name: Integration
|
|
runs-on: ubuntu-latest
|
|
needs: test
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
|
|
|
|
- name: Build NORA
|
|
run: cargo build --release --package nora-registry
|
|
|
|
- name: Start NORA
|
|
run: |
|
|
NORA_STORAGE_PATH=/tmp/nora-data ./target/release/nora &
|
|
for i in $(seq 1 15); do
|
|
curl -sf http://localhost:4000/health && break || sleep 2
|
|
done
|
|
curl -sf http://localhost:4000/health | jq .
|
|
|
|
- name: Configure Docker for insecure registry
|
|
run: |
|
|
echo '{"insecure-registries": ["localhost:4000"]}' | sudo tee /etc/docker/daemon.json
|
|
sudo systemctl restart docker
|
|
sleep 2
|
|
|
|
- name: Docker — push and pull image
|
|
run: |
|
|
docker pull alpine:3.20
|
|
docker tag alpine:3.20 localhost:4000/test/alpine:integration
|
|
docker push localhost:4000/test/alpine:integration
|
|
docker rmi localhost:4000/test/alpine:integration
|
|
docker pull localhost:4000/test/alpine:integration
|
|
echo "Docker push/pull OK"
|
|
|
|
- name: Docker — verify catalog and tags
|
|
run: |
|
|
curl -sf http://localhost:4000/v2/_catalog | jq .
|
|
curl -sf http://localhost:4000/v2/test/alpine/tags/list | jq .
|
|
|
|
- name: npm — verify registry endpoint
|
|
run: |
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/npm/lodash)
|
|
echo "npm endpoint returned: $STATUS"
|
|
[ "$STATUS" != "000" ] && echo "npm endpoint OK" || (echo "npm endpoint unreachable" && exit 1)
|
|
|
|
- name: Maven — deploy and download artifact
|
|
run: |
|
|
echo "test-artifact-content-$(date +%s)" > /tmp/test-artifact.jar
|
|
CHECKSUM=$(sha256sum /tmp/test-artifact.jar | cut -d' ' -f1)
|
|
curl -sf -X PUT --data-binary @/tmp/test-artifact.jar \
|
|
http://localhost:4000/maven2/com/example/test-lib/1.0.0/test-lib-1.0.0.jar
|
|
curl -sf -o /tmp/downloaded.jar \
|
|
http://localhost:4000/maven2/com/example/test-lib/1.0.0/test-lib-1.0.0.jar
|
|
DOWNLOAD_CHECKSUM=$(sha256sum /tmp/downloaded.jar | cut -d' ' -f1)
|
|
[ "$CHECKSUM" = "$DOWNLOAD_CHECKSUM" ] && echo "Maven deploy/download OK" || (echo "Checksum mismatch!" && exit 1)
|
|
|
|
- name: PyPI — verify simple index
|
|
run: |
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/simple/)
|
|
echo "PyPI simple index returned: $STATUS"
|
|
[ "$STATUS" = "200" ] && echo "PyPI endpoint OK" || (echo "Expected 200, got $STATUS" && exit 1)
|
|
|
|
- name: Cargo — verify registry API responds
|
|
run: |
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/cargo/api/v1/crates/serde)
|
|
echo "Cargo API returned: $STATUS"
|
|
[ "$STATUS" != "000" ] && echo "Cargo endpoint OK" || (echo "Cargo endpoint unreachable" && exit 1)
|
|
|
|
- name: API — health, ready, metrics
|
|
run: |
|
|
curl -sf http://localhost:4000/health | jq .status
|
|
curl -sf http://localhost:4000/ready
|
|
curl -sf http://localhost:4000/metrics | head -5
|
|
echo "API checks OK"
|
|
|
|
- name: Stop NORA
|
|
if: always()
|
|
run: pkill nora || true
|