diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 959c0497..48f68ab6 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -27,6 +27,11 @@ on: required: false type: string default: "build-amd64" + cargo-version: + description: "Pre-computed cargo version (skips internal git-based computation)" + required: false + type: string + default: "" env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -69,7 +74,11 @@ jobs: id: version run: | set -eu - echo "cargo_version=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT" + if [[ -n "${{ inputs.cargo-version }}" ]]; then + echo "cargo_version=${{ inputs.cargo-version }}" >> "$GITHUB_OUTPUT" + else + echo "cargo_version=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT" + fi - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 842e4eba..6172fab9 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -14,15 +14,52 @@ defaults: shell: bash jobs: + # --------------------------------------------------------------------------- + # Compute all versions once at the start to avoid git-describe race conditions + # --------------------------------------------------------------------------- + compute-versions: + name: Compute Versions + runs-on: build-amd64 + timeout-minutes: 5 + container: + image: ghcr.io/nvidia/openshell/ci:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + outputs: + python_version: ${{ steps.v.outputs.python }} + cargo_version: ${{ steps.v.outputs.cargo }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Fetch tags + run: git fetch --tags --force + + - name: Compute all versions + id: v + run: | + set -euo pipefail + echo "python=$(uv run python tasks/scripts/release.py get-version --python)" >> "$GITHUB_OUTPUT" + echo "cargo=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT" + build-gateway: + needs: [compute-versions] uses: ./.github/workflows/docker-build.yml with: component: gateway + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} build-cluster: + needs: [compute-versions] uses: ./.github/workflows/docker-build.yml with: component: cluster + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} tag-ghcr-dev: name: Tag GHCR Images as Dev @@ -47,11 +84,11 @@ jobs: build-python-wheels: name: Stage Python Wheels - needs: [build-gateway, build-cluster] + needs: [compute-versions, build-gateway, build-cluster] runs-on: build-amd64 timeout-minutes: 120 outputs: - wheel_version: ${{ steps.version.outputs.wheel_version }} + wheel_version: ${{ needs.compute-versions.outputs.python_version }} s3_prefix: ${{ steps.upload.outputs.s3_prefix }} container: image: ghcr.io/nvidia/openshell/ci:latest @@ -83,30 +120,21 @@ jobs: - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Fetch tags - run: git fetch --tags --force - - - name: Compute Python version - id: version - run: | - set -euo pipefail - WHEEL_VERSION=$(uv run python tasks/scripts/release.py get-version --python) - echo "wheel_version=${WHEEL_VERSION}" >> "$GITHUB_OUTPUT" + - name: Sync Python dependencies + run: uv sync - name: Build Python wheels run: | set -euo pipefail - WHEEL_VERSION="${{ steps.version.outputs.wheel_version }}" - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) - OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run python:build:multiarch - OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run python:build:macos + OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:multiarch + OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:macos ls -la target/wheels/*.whl - name: Upload wheels to S3 id: upload run: | set -euo pipefail - WHEEL_VERSION="${{ steps.version.outputs.wheel_version }}" + WHEEL_VERSION="${{ needs.compute-versions.outputs.python_version }}" S3_PREFIX="openshell/${WHEEL_VERSION}" aws s3 cp target/wheels/ "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" --recursive --exclude "*" --include "*.whl" aws s3 ls "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" @@ -190,6 +218,7 @@ jobs: # --------------------------------------------------------------------------- build-cli-linux: name: Build CLI (Linux ${{ matrix.arch }}) + needs: [compute-versions] strategy: matrix: include: @@ -232,13 +261,6 @@ jobs: cache-directories: .cache/sccache cache-targets: "true" - - name: Compute version - id: version - run: | - set -euo pipefail - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) - echo "cargo_version=${CARGO_VERSION}" >> "$GITHUB_OUTPUT" - - name: Install musl toolchain run: | set -euo pipefail @@ -258,10 +280,10 @@ jobs: sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-cli", "crates/openshell-core", "crates/openshell-bootstrap", "crates/openshell-policy", "crates/openshell-providers", "crates/openshell-tui"]|' Cargo.toml - name: Patch workspace version - if: steps.version.outputs.cargo_version != '' + if: needs.compute-versions.outputs.cargo_version != '' run: | set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ steps.version.outputs.cargo_version }}"'"/}' Cargo.toml + sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - name: Build ${{ matrix.target }} run: mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli @@ -290,6 +312,7 @@ jobs: # --------------------------------------------------------------------------- build-cli-macos: name: Build CLI (macOS) + needs: [compute-versions] runs-on: build-amd64 timeout-minutes: 60 container: @@ -320,19 +343,12 @@ jobs: - name: Set up Docker Buildx uses: ./.github/actions/setup-buildx - - name: Compute version - id: version - run: | - set -euo pipefail - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) - echo "cargo_version=${CARGO_VERSION}" >> "$GITHUB_OUTPUT" - - name: Build macOS binary via Docker run: | set -euo pipefail docker buildx build \ --file deploy/docker/Dockerfile.cli-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ steps.version.outputs.cargo_version }}" \ + --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ --build-arg OPENSHELL_IMAGE_TAG=dev \ --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ --target binary \ diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index b710b4e3..6396b17c 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -14,19 +14,59 @@ defaults: shell: bash jobs: + # --------------------------------------------------------------------------- + # Compute all versions once at the start to avoid git-describe race conditions + # --------------------------------------------------------------------------- + compute-versions: + name: Compute Versions + runs-on: build-amd64 + timeout-minutes: 5 + container: + image: ghcr.io/nvidia/openshell/ci:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + outputs: + python_version: ${{ steps.v.outputs.python }} + cargo_version: ${{ steps.v.outputs.cargo }} + # Semver without 'v' prefix (e.g. 0.6.0), used for image tags and release body + semver: ${{ steps.v.outputs.semver }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Fetch tags + run: git fetch --tags --force + + - name: Compute all versions + id: v + run: | + set -euo pipefail + echo "python=$(uv run python tasks/scripts/release.py get-version --python)" >> "$GITHUB_OUTPUT" + echo "cargo=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT" + echo "semver=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + build-gateway: + needs: [compute-versions] uses: ./.github/workflows/docker-build.yml with: component: gateway + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} build-cluster: + needs: [compute-versions] uses: ./.github/workflows/docker-build.yml with: component: cluster + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} tag-ghcr-release: name: Tag GHCR Images for Release - needs: [build-gateway, build-cluster] + needs: [compute-versions, build-gateway, build-cluster] runs-on: build-amd64 timeout-minutes: 10 steps: @@ -37,7 +77,7 @@ jobs: run: | set -euo pipefail REGISTRY="ghcr.io/nvidia/openshell" - VERSION="${GITHUB_REF_NAME#v}" + VERSION="${{ needs.compute-versions.outputs.semver }}" for component in gateway cluster; do echo "Tagging ${REGISTRY}/${component}:${{ github.sha }} as ${VERSION} and latest..." docker buildx imagetools create \ @@ -52,11 +92,11 @@ jobs: build-python-wheels: name: Stage Python Wheels - needs: [build-gateway, build-cluster] + needs: [compute-versions, build-gateway, build-cluster] runs-on: build-amd64 timeout-minutes: 120 outputs: - wheel_version: ${{ steps.version.outputs.wheel_version }} + wheel_version: ${{ needs.compute-versions.outputs.python_version }} s3_prefix: ${{ steps.upload.outputs.s3_prefix }} container: image: ghcr.io/nvidia/openshell/ci:latest @@ -69,6 +109,7 @@ jobs: env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} + OPENSHELL_IMAGE_TAG: ${{ needs.compute-versions.outputs.semver }} NAV_PYPI_S3_BUCKET: navigator-pypi-artifacts # TODO: rename bucket to openshell-pypi-artifacts AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -87,30 +128,21 @@ jobs: - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Fetch tags - run: git fetch --tags --force - - - name: Compute Python version - id: version - run: | - set -euo pipefail - WHEEL_VERSION=$(uv run python tasks/scripts/release.py get-version --python) - echo "wheel_version=${WHEEL_VERSION}" >> "$GITHUB_OUTPUT" + - name: Sync Python dependencies + run: uv sync - name: Build Python wheels run: | set -euo pipefail - WHEEL_VERSION="${{ steps.version.outputs.wheel_version }}" - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) - OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run python:build:multiarch - OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run python:build:macos + OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:multiarch + OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:macos ls -la target/wheels/*.whl - name: Upload wheels to S3 id: upload run: | set -euo pipefail - WHEEL_VERSION="${{ steps.version.outputs.wheel_version }}" + WHEEL_VERSION="${{ needs.compute-versions.outputs.python_version }}" S3_PREFIX="openshell/${WHEEL_VERSION}" aws s3 cp target/wheels/ "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" --recursive --exclude "*" --include "*.whl" aws s3 ls "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" @@ -194,6 +226,7 @@ jobs: # --------------------------------------------------------------------------- build-cli-linux: name: Build CLI (Linux ${{ matrix.arch }}) + needs: [compute-versions] strategy: matrix: include: @@ -214,6 +247,7 @@ jobs: env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} + OPENSHELL_IMAGE_TAG: ${{ needs.compute-versions.outputs.semver }} steps: - uses: actions/checkout@v4 with: @@ -235,13 +269,6 @@ jobs: cache-directories: .cache/sccache cache-targets: "true" - - name: Compute version - id: version - run: | - set -euo pipefail - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) - echo "cargo_version=${CARGO_VERSION}" >> "$GITHUB_OUTPUT" - - name: Install musl toolchain run: | set -euo pipefail @@ -261,10 +288,10 @@ jobs: sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-cli", "crates/openshell-core", "crates/openshell-bootstrap", "crates/openshell-policy", "crates/openshell-providers", "crates/openshell-tui"]|' Cargo.toml - name: Patch workspace version - if: steps.version.outputs.cargo_version != '' + if: needs.compute-versions.outputs.cargo_version != '' run: | set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ steps.version.outputs.cargo_version }}"'"/}' Cargo.toml + sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - name: Build ${{ matrix.target }} run: mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli @@ -293,6 +320,7 @@ jobs: # --------------------------------------------------------------------------- build-cli-macos: name: Build CLI (macOS) + needs: [compute-versions] runs-on: build-amd64 timeout-minutes: 60 container: @@ -323,19 +351,13 @@ jobs: - name: Set up Docker Buildx uses: ./.github/actions/setup-buildx - - name: Compute version - id: version - run: | - set -euo pipefail - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) - echo "cargo_version=${CARGO_VERSION}" >> "$GITHUB_OUTPUT" - - name: Build macOS binary via Docker run: | set -euo pipefail docker buildx build \ --file deploy/docker/Dockerfile.cli-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ steps.version.outputs.cargo_version }}" \ + --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ + --build-arg OPENSHELL_IMAGE_TAG="${{ needs.compute-versions.outputs.semver }}" \ --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ --target binary \ --output type=local,dest=out/ \ @@ -361,16 +383,12 @@ jobs: # --------------------------------------------------------------------------- release: name: Release - needs: [build-cli-linux, build-cli-macos, publish-python, tag-ghcr-release] + needs: [compute-versions, build-cli-linux, build-cli-macos, publish-python, tag-ghcr-release] runs-on: build-amd64 timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - name: Compute version - id: version - run: echo "semver=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - - name: Download all CLI artifacts uses: actions/download-artifact@v4 with: @@ -415,8 +433,8 @@ jobs: ### Docker images ```bash - docker pull ghcr.io/nvidia/openshell/gateway:${{ steps.version.outputs.semver }} - docker pull ghcr.io/nvidia/openshell/cluster:${{ steps.version.outputs.semver }} + docker pull ghcr.io/nvidia/openshell/gateway:${{ needs.compute-versions.outputs.semver }} + docker pull ghcr.io/nvidia/openshell/cluster:${{ needs.compute-versions.outputs.semver }} ``` ### Assets diff --git a/crates/openshell-bootstrap/src/image.rs b/crates/openshell-bootstrap/src/image.rs index 840856f5..6e2b0f94 100644 --- a/crates/openshell-bootstrap/src/image.rs +++ b/crates/openshell-bootstrap/src/image.rs @@ -17,11 +17,12 @@ const PULL_REGISTRY_DEFAULT_TAG: &str = "latest"; /// Image tag baked in at compile time. /// /// Set via `OPENSHELL_IMAGE_TAG` env var during `cargo build`: -/// - Defaults to `"latest"` when unset (release builds, bare `cargo build`). -/// - Set to `"dev"` for dev CLI builds targeting locally-built images. +/// - Defaults to `"dev"` when unset (local builds, `mise run docker:build`). +/// - CI sets this explicitly: `"dev"` for main-branch builds, the version +/// string (e.g. `"0.6.0"`) for tagged releases. pub const DEFAULT_IMAGE_TAG: &str = match option_env!("OPENSHELL_IMAGE_TAG") { Some(tag) => tag, - None => "latest", + None => "dev", }; // --------------------------------------------------------------------------- diff --git a/tasks/ci.toml b/tasks/ci.toml index bccda526..515b5abd 100644 --- a/tasks/ci.toml +++ b/tasks/ci.toml @@ -13,10 +13,6 @@ description = "Build all Rust crates in release mode" run = "cargo build --workspace --release" hide = true -["build:dev"] -description = "Build CLI with dev image tag (for local development)" -run = "OPENSHELL_IMAGE_TAG=dev cargo build -p openshell-cli" - [check] description = "Run fast compile and type checks" depends = ["rust:check", "python:typecheck"]