Release Canary #16
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: Release Canary | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag to test (e.g. dev, v1.2.3)" | |
| required: true | |
| type: string | |
| workflow_run: | |
| workflows: ["Release Dev", "Release Tag"] | |
| types: [completed] | |
| permissions: | |
| contents: read | |
| packages: read | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Verify the default install path (no OPENSHELL_VERSION) resolves to latest | |
| # --------------------------------------------------------------------------- | |
| install-default: | |
| name: Install default (${{ matrix.arch }}) | |
| if: >- | |
| github.event.workflow_run.conclusion == 'success' | |
| && github.event.workflow_run.name == 'Release Tag' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - arch: amd64 | |
| runner: linux-amd64-cpu8 | |
| - arch: arm64 | |
| runner: linux-arm64-cpu8 | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 10 | |
| container: | |
| image: ghcr.io/nvidia/openshell/ci:latest | |
| credentials: | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - name: Install CLI (default / latest) | |
| run: | | |
| set -euo pipefail | |
| curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh | |
| - name: Verify CLI installation | |
| run: | | |
| set -euo pipefail | |
| command -v openshell | |
| ACTUAL="$(openshell --version)" | |
| echo "Installed: $ACTUAL" | |
| # This job only runs after Release Tag, so the triggering tag | |
| # should match the latest release the default installer resolves to. | |
| TAG="${{ github.event.workflow_run.head_branch }}" | |
| if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| EXPECTED="${TAG#v}" | |
| if [[ "$ACTUAL" != *"$EXPECTED"* ]]; then | |
| echo "::error::Version mismatch: expected '$EXPECTED' in '$ACTUAL'" | |
| exit 1 | |
| fi | |
| echo "Version check passed: found $EXPECTED in output" | |
| fi | |
| install-dev: | |
| name: Install Debian package (${{ matrix.arch }}) | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - arch: amd64 | |
| runner: linux-amd64-cpu8 | |
| - arch: arm64 | |
| runner: linux-arm64-cpu8 | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 10 | |
| container: | |
| image: ghcr.io/nvidia/openshell/ci:latest | |
| credentials: | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - name: Determine release tag | |
| id: release | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| WORKFLOW_NAME="${{ github.event.workflow_run.name }}" | |
| if [ "$WORKFLOW_NAME" = "Release Dev" ]; then | |
| echo "tag=dev" >> "$GITHUB_OUTPUT" | |
| elif [ "$WORKFLOW_NAME" = "Release Tag" ]; then | |
| TAG="${{ github.event.workflow_run.head_branch }}" | |
| if [ -z "$TAG" ]; then | |
| echo "::error::Could not determine release tag from workflow_run" | |
| exit 1 | |
| fi | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::Unexpected triggering workflow: ${WORKFLOW_NAME}" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Install Debian package | |
| run: | | |
| set -euo pipefail | |
| curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install-dev.sh \ | |
| | OPENSHELL_VERSION=${{ steps.release.outputs.tag }} sh | |
| - name: Verify gateway and VM driver versions | |
| run: | | |
| set -euo pipefail | |
| command -v openshell-gateway | |
| test -x /usr/libexec/openshell/openshell-driver-vm | |
| GATEWAY_ACTUAL="$(openshell-gateway --version)" | |
| DRIVER_ACTUAL="$(/usr/libexec/openshell/openshell-driver-vm --version)" | |
| echo "Gateway: ${GATEWAY_ACTUAL}" | |
| echo "Driver: ${DRIVER_ACTUAL}" | |
| TAG="${{ steps.release.outputs.tag }}" | |
| if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| EXPECTED="${TAG#v}" | |
| for actual in "$GATEWAY_ACTUAL" "$DRIVER_ACTUAL"; do | |
| if [[ "$actual" != *"$EXPECTED"* ]]; then | |
| echo "::error::Version mismatch: expected '$EXPECTED' in '$actual'" | |
| exit 1 | |
| fi | |
| done | |
| echo "Version check passed: found $EXPECTED in both binaries" | |
| else | |
| echo "Non-release tag ($TAG), skipping version check" | |
| fi | |
| canary: | |
| name: Canary package gateway (${{ matrix.arch }}) | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - arch: amd64 | |
| runner: linux-amd64-cpu8 | |
| - arch: arm64 | |
| runner: linux-arm64-cpu8 | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| env: | |
| OPENSHELL_REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| OPENSHELL_CANARY_PORT: "17670" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Determine release tag | |
| id: release | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| WORKFLOW_NAME="${{ github.event.workflow_run.name }}" | |
| if [ "$WORKFLOW_NAME" = "Release Dev" ]; then | |
| echo "tag=dev" >> "$GITHUB_OUTPUT" | |
| elif [ "$WORKFLOW_NAME" = "Release Tag" ]; then | |
| TAG="${{ github.event.workflow_run.head_branch }}" | |
| if [ -z "$TAG" ]; then | |
| echo "::error::Could not determine release tag from workflow_run" | |
| exit 1 | |
| fi | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::Unexpected triggering workflow: ${WORKFLOW_NAME}" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Install Debian package | |
| run: | | |
| set -euo pipefail | |
| curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install-dev.sh \ | |
| | OPENSHELL_VERSION=${{ steps.release.outputs.tag }} sh | |
| - name: Verify package binaries | |
| run: | | |
| set -euo pipefail | |
| command -v openshell | |
| command -v openshell-gateway | |
| test -x /usr/libexec/openshell/openshell-driver-vm | |
| CLI_ACTUAL="$(openshell --version)" | |
| GATEWAY_ACTUAL="$(openshell-gateway --version)" | |
| DRIVER_ACTUAL="$(/usr/libexec/openshell/openshell-driver-vm --version)" | |
| echo "CLI: ${CLI_ACTUAL}" | |
| echo "Gateway: ${GATEWAY_ACTUAL}" | |
| echo "Driver: ${DRIVER_ACTUAL}" | |
| TAG="${{ steps.release.outputs.tag }}" | |
| if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| EXPECTED="${TAG#v}" | |
| for actual in "$CLI_ACTUAL" "$GATEWAY_ACTUAL" "$DRIVER_ACTUAL"; do | |
| if [[ "$actual" != *"$EXPECTED"* ]]; then | |
| echo "::error::Version mismatch: expected '$EXPECTED' in '$actual'" | |
| exit 1 | |
| fi | |
| done | |
| echo "Version check passed: found $EXPECTED in package binaries" | |
| else | |
| echo "Non-release tag ($TAG), skipping version check" | |
| fi | |
| - name: Start packaged gateway | |
| run: | | |
| set -euo pipefail | |
| # The CLI no longer owns gateway lifecycle. In CI we start the | |
| # gateway binary installed by the Debian package directly, using the | |
| # Docker driver so this canary can launch a real sandbox. | |
| systemctl --user stop openshell-gateway >/dev/null 2>&1 || true | |
| STATE_DIR="$(mktemp -d)" | |
| LOG="${STATE_DIR}/openshell-gateway.log" | |
| echo "GATEWAY_LOG=${LOG}" >> "$GITHUB_ENV" | |
| echo "GATEWAY_STATE_DIR=${STATE_DIR}" >> "$GITHUB_ENV" | |
| OPENSHELL_BIND_ADDRESS=0.0.0.0 \ | |
| OPENSHELL_SERVER_PORT="${OPENSHELL_CANARY_PORT}" \ | |
| OPENSHELL_DISABLE_TLS=true \ | |
| OPENSHELL_DISABLE_GATEWAY_AUTH=true \ | |
| OPENSHELL_DRIVERS=docker \ | |
| OPENSHELL_DB_URL="sqlite:${STATE_DIR}/openshell.db?mode=rwc" \ | |
| OPENSHELL_GRPC_ENDPOINT="http://host.openshell.internal:${OPENSHELL_CANARY_PORT}" \ | |
| OPENSHELL_SSH_GATEWAY_HOST=127.0.0.1 \ | |
| OPENSHELL_SSH_GATEWAY_PORT="${OPENSHELL_CANARY_PORT}" \ | |
| OPENSHELL_SANDBOX_NAMESPACE="canary-${{ matrix.arch }}-${{ github.run_id }}" \ | |
| nohup openshell-gateway >"${LOG}" 2>&1 & | |
| PID=$! | |
| echo "GATEWAY_PID=${PID}" >> "$GITHUB_ENV" | |
| for _ in $(seq 1 60); do | |
| if curl -fsS "http://127.0.0.1:${OPENSHELL_CANARY_PORT}/healthz" >/dev/null; then | |
| break | |
| fi | |
| if ! kill -0 "$PID" 2>/dev/null; then | |
| echo "::error::openshell-gateway exited before becoming healthy" | |
| cat "$LOG" | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| curl -fsS "http://127.0.0.1:${OPENSHELL_CANARY_PORT}/healthz" | |
| openshell gateway remove local >/dev/null 2>&1 || true | |
| openshell gateway add "http://127.0.0.1:${OPENSHELL_CANARY_PORT}" --local --name local | |
| - name: Run canary test | |
| run: | | |
| set -euo pipefail | |
| echo "Creating sandbox and running 'echo hello world'..." | |
| OUTPUT=$(openshell sandbox create --no-keep --no-tty -- echo "hello world" 2>&1) || { | |
| EXIT_CODE=$? | |
| echo "::error::openshell sandbox create failed with exit code ${EXIT_CODE}" | |
| echo "$OUTPUT" | |
| exit $EXIT_CODE | |
| } | |
| echo "$OUTPUT" | |
| if echo "$OUTPUT" | grep -q "hello world"; then | |
| echo "Canary test passed: 'hello world' found in output" | |
| else | |
| echo "::error::Canary test failed: 'hello world' not found in output" | |
| exit 1 | |
| fi | |
| - name: Stop packaged gateway | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${GATEWAY_PID:-}" ]; then | |
| kill "$GATEWAY_PID" >/dev/null 2>&1 || true | |
| fi | |
| if [ "${{ job.status }}" != "success" ] && [ -n "${GATEWAY_LOG:-}" ] && [ -f "$GATEWAY_LOG" ]; then | |
| echo "Gateway log:" | |
| cat "$GATEWAY_LOG" | |
| fi |