Merge pull request #297 from kalamdb/031-oidc-local-auth #320
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| permissions: | |
| contents: write | |
| checks: write | |
| pull-requests: write | |
| on: | |
| push: | |
| branches: [ main ] | |
| workflow_dispatch: # Allow manual trigger | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_VERSION: "1.92.0" | |
| RUSTC_WRAPPER: "" | |
| CARGO_BUILD_RUSTC_WRAPPER: "" | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| jobs: | |
| fmt: | |
| name: Format Check | |
| runs-on: ubuntu-latest | |
| if: false # Only run on manual trigger | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| components: rustfmt | |
| - name: Run rustfmt | |
| run: cargo fmt --all -- --check | |
| clippy: | |
| name: Clippy Check | |
| runs-on: ubuntu-latest | |
| if: false # Only run on manual trigger | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| components: clippy | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| clang \ | |
| libclang-dev \ | |
| pkg-config \ | |
| libssl-dev | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache cargo build | |
| uses: actions/cache@v5 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-build-target- | |
| - name: Run clippy | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| # test: | |
| # name: Test Suite | |
| # runs-on: ubuntu-latest | |
| # steps: | |
| # - name: Checkout code | |
| # uses: actions/checkout@v6 | |
| # - name: Install Rust toolchain | |
| # uses: dtolnay/rust-toolchain@stable | |
| # with: | |
| # toolchain: ${{ env.RUST_VERSION }} | |
| # - name: Install system dependencies | |
| # run: | | |
| # sudo apt-get update | |
| # sudo apt-get install -y --no-install-recommends \ | |
| # clang \ | |
| # libclang-dev \ | |
| # pkg-config \ | |
| # libssl-dev | |
| # - name: Cache cargo registry | |
| # uses: actions/cache@v5 | |
| # with: | |
| # path: ~/.cargo/registry | |
| # key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| # restore-keys: | | |
| # ${{ runner.os }}-cargo-registry- | |
| # - name: Cache cargo index | |
| # uses: actions/cache@v5 | |
| # with: | |
| # path: ~/.cargo/git | |
| # key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| # restore-keys: | | |
| # ${{ runner.os }}-cargo-index- | |
| # - name: Cache cargo build | |
| # uses: actions/cache@v5 | |
| # with: | |
| # path: target | |
| # key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} | |
| # restore-keys: | | |
| # ${{ runner.os }}-cargo-build-target- | |
| # - name: Run tests | |
| # run: cargo test --workspace --all-features | |
| test: | |
| name: Test Suite | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| clang \ | |
| libclang-dev \ | |
| pkg-config \ | |
| libssl-dev | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache cargo build | |
| uses: actions/cache@v5 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-build-target- | |
| - name: Install cargo-nextest | |
| run: | | |
| curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin | |
| - name: Run tests (nextest) | |
| id: nextest | |
| run: | | |
| cargo nextest run \ | |
| --profile ci \ | |
| --workspace \ | |
| --exclude kalam-pg-extension \ | |
| --no-fail-fast \ | |
| --failure-output immediate-final | |
| continue-on-error: true | |
| - name: Normalize JUnit suite durations for test-reporter | |
| if: always() && hashFiles('target/nextest/ci/junit.xml') != '' | |
| run: | | |
| python3 << 'PYTHON_SCRIPT' | |
| import math | |
| import xml.etree.ElementTree as ET | |
| junit_path = "target/nextest/ci/junit.xml" | |
| def parse_time(raw): | |
| if raw is None: | |
| return None | |
| try: | |
| value = float(raw) | |
| except ValueError: | |
| return None | |
| return value if math.isfinite(value) else None | |
| def format_time(value): | |
| return f"{value:.6f}".rstrip("0").rstrip(".") or "0" | |
| tree = ET.parse(junit_path) | |
| root = tree.getroot() | |
| suites = [root] if root.tag == "testsuite" else root.findall("./testsuite") | |
| suite_times = [] | |
| changed = False | |
| for testsuite in suites: | |
| suite_time = parse_time(testsuite.get("time")) | |
| if suite_time is None: | |
| suite_time = 0.0 | |
| for testcase in testsuite.findall("./testcase"): | |
| case_time = parse_time(testcase.get("time")) | |
| if case_time is not None: | |
| suite_time += case_time | |
| for attempt in testcase: | |
| attempt_time = parse_time(attempt.get("time")) | |
| if attempt_time is not None: | |
| suite_time += attempt_time | |
| testsuite.set("time", format_time(suite_time)) | |
| changed = True | |
| suite_times.append(suite_time) | |
| report_time = parse_time(root.get("time")) | |
| if report_time is None and suite_times: | |
| root.set("time", format_time(sum(suite_times))) | |
| changed = True | |
| if changed: | |
| tree.write(junit_path, encoding="utf-8", xml_declaration=True) | |
| print(f"Normalized JUnit durations in {junit_path}") | |
| else: | |
| print(f"JUnit durations already present in {junit_path}") | |
| PYTHON_SCRIPT | |
| - name: Parse test results and generate badge | |
| if: always() && hashFiles('target/nextest/ci/junit.xml') != '' | |
| run: | | |
| JUNIT_FILE="target/nextest/ci/junit.xml" | |
| if [ ! -f "$JUNIT_FILE" ]; then | |
| echo "Error: JUnit XML report not found at $JUNIT_FILE" | |
| exit 1 | |
| fi | |
| # Parse JUnit XML using Python (available in ubuntu-latest) | |
| eval $(python3 << 'PYTHON_SCRIPT' | |
| import xml.etree.ElementTree as ET | |
| import sys | |
| try: | |
| tree = ET.parse("target/nextest/ci/junit.xml") | |
| root = tree.getroot() | |
| total = 0 | |
| failures = 0 | |
| errors = 0 | |
| # Sum up all testsuite elements | |
| for testsuite in root.findall('.//testsuite'): | |
| total += int(testsuite.get('tests', 0)) | |
| failures += int(testsuite.get('failures', 0)) | |
| errors += int(testsuite.get('errors', 0)) | |
| failed = failures + errors | |
| passed = total - failed | |
| print(f"TOTAL={total}") | |
| print(f"PASSED={passed}") | |
| print(f"FAILED={failed}") | |
| if total == 0: | |
| print("Error: No tests found in JUnit report", file=sys.stderr) | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error parsing JUnit XML: {e}", file=sys.stderr) | |
| sys.exit(1) | |
| PYTHON_SCRIPT | |
| ) | |
| echo "Tests: $PASSED passed, $FAILED failed (total: $TOTAL)" | |
| # Determine badge color and message | |
| if [ "$FAILED" -eq "0" ]; then | |
| COLOR="brightgreen" | |
| MESSAGE="$PASSED/$TOTAL passed" | |
| else | |
| COLOR="red" | |
| MESSAGE="$PASSED/$TOTAL passed, $FAILED failed" | |
| fi | |
| echo "Badge: $MESSAGE ($COLOR)" | |
| # Generate badge JSON | |
| mkdir -p .github/badges | |
| cat > .github/badges/tests.json << EOF | |
| { | |
| "schemaVersion": 1, | |
| "label": "overall tests ($TOTAL)", | |
| "message": "$MESSAGE", | |
| "color": "$COLOR" | |
| } | |
| EOF | |
| cat .github/badges/tests.json | |
| # Fail the step if tests failed | |
| if [ "$FAILED" -ne "0" ]; then | |
| exit 1 | |
| fi | |
| - name: Fail if JUnit report is missing after a successful nextest run | |
| if: always() && steps.nextest.outcome == 'success' && hashFiles('target/nextest/ci/junit.xml') == '' | |
| run: | | |
| echo "Error: JUnit XML report not found at target/nextest/ci/junit.xml" | |
| exit 1 | |
| - name: Publish test report | |
| if: always() && hashFiles('target/nextest/ci/junit.xml') != '' | |
| uses: dorny/test-reporter@v1 | |
| with: | |
| name: nextest | |
| path: target/nextest/ci/junit.xml | |
| reporter: java-junit | |
| - name: Fail workflow if nextest did not complete successfully | |
| if: always() && steps.nextest.outcome != 'success' | |
| run: | | |
| echo "nextest failed before completing the workspace suite" | |
| exit 1 | |
| - name: Commit badge | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| run: | | |
| set -euo pipefail | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| git checkout -B ci-test-badge-update | |
| git add .github/badges/tests.json | |
| if git diff --staged --quiet; then | |
| echo "Test badge file already up to date" | |
| exit 0 | |
| fi | |
| git commit -m "Update test badge [skip ci]" | |
| git push origin HEAD:main | |
| license_compliance: | |
| name: License Compliance | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| - name: Install cargo-license | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cargo install cargo-license --locked | |
| - name: Generate Rust runtime license report | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/license | |
| cargo license -j --avoid-dev-deps --avoid-build-deps -o target/license/rust-runtime-licenses.json | |
| - name: Install UI dependencies | |
| shell: bash | |
| working-directory: ui | |
| run: | | |
| set -euo pipefail | |
| npm install --no-audit --no-fund | |
| - name: Generate UI production license report | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cd ui | |
| npx --yes license-checker --production --excludePrivatePackages --json > ../target/license/ui-licenses.json | |
| - name: Install SDK dependencies | |
| shell: bash | |
| working-directory: link/sdks/typescript/client | |
| run: | | |
| set -euo pipefail | |
| npm install --no-audit --no-fund | |
| - name: Generate SDK production license report | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cd link/sdks/typescript/client | |
| npx --yes license-checker --production --json > "$GITHUB_WORKSPACE/target/license/sdk-licenses.json" | |
| - name: Install npm CLI dependencies | |
| shell: bash | |
| working-directory: link/sdks/typescript/cli | |
| run: | | |
| set -euo pipefail | |
| npm install --ignore-scripts --no-audit --no-fund | |
| - name: Generate npm CLI production license report | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cd link/sdks/typescript/cli | |
| npx --yes license-checker --production --json > "$GITHUB_WORKSPACE/target/license/npm-cli-licenses.json" | |
| - name: Validate licenses and generate third-party report | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python3 scripts/license_compliance.py \ | |
| --policy cargo-license-check.toml \ | |
| --rust-report target/license/rust-runtime-licenses.json \ | |
| --ui-report target/license/ui-licenses.json \ | |
| --sdk-report target/license/sdk-licenses.json \ | |
| --cli-report target/license/npm-cli-licenses.json \ | |
| --output target/license/THIRD_PARTY_LICENSES.md | |
| - name: Upload license reports | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: license-reports | |
| path: target/license/ | |
| if-no-files-found: error |