diff --git a/.github/workflows/dependency-stability.yml b/.github/workflows/dependency-stability.yml new file mode 100644 index 000000000..4ea5c2345 --- /dev/null +++ b/.github/workflows/dependency-stability.yml @@ -0,0 +1,108 @@ +name: Dependency Lock Stability + +on: + pull_request: + branches: [ main ] + paths: + - "**/go.mod" + - "**/go.sum" + - "**/uv.lock" + - "**/pyproject.toml" + - "**/package.json" + - "**/pnpm-lock.yaml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + go-modules: + name: go.mod tidy (${{ matrix.module }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - module: components/execd + - module: components/egress + - module: components/ingress + - module: components/internal + - module: components/nodeagent + - module: kubernetes + - module: sdks/sandbox/go + - module: sdks/sandbox/go/poolredis + - module: tests/go + - module: examples/chrome + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25.9" + + - name: Check go.mod is tidy + working-directory: ${{ matrix.module }} + run: | + go mod tidy + git diff --exit-code -- go.mod go.sum + + uv-lockfiles: + name: uv.lock (${{ matrix.project }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - project: server + - project: cli + - project: sdks/sandbox/python + - project: sdks/code-interpreter/python + - project: sdks/mcp/sandbox/python + - project: tests/python + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + + - name: Check uv.lock is up to date + working-directory: ${{ matrix.project }} + run: | + uv lock --check + + js-lockfile: + name: pnpm-lock.yaml (tests/javascript) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.0 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + cache-dependency-path: tests/javascript/pnpm-lock.yaml + + - name: Check pnpm-lock.yaml is in sync (tests/javascript) + working-directory: tests/javascript + run: | + pnpm install --frozen-lockfile --lockfile-only --ignore-scripts + + - name: Check pnpm-lock.yaml is in sync (docs) + working-directory: docs + run: | + pnpm install --frozen-lockfile --lockfile-only --ignore-scripts diff --git a/.github/workflows/release-generic.yml b/.github/workflows/release-generic.yml deleted file mode 100644 index 007489b62..000000000 --- a/.github/workflows/release-generic.yml +++ /dev/null @@ -1,191 +0,0 @@ -name: Generic Release - -on: - workflow_dispatch: - inputs: - target: - description: "Release target key" - required: true - type: choice - options: - - js/sandbox - - js/code-interpreter - - python/sandbox - - python/code-interpreter - - python/mcp/sandbox - - java/sandbox - - csharp/sandbox - - csharp/code-interpreter - - sdks/sandbox/go - - cli - - server - - docker/execd - - docker/nodeagent - - docker/code-interpreter - - docker/ingress - - docker/egress - - k8s/controller - - k8s/task-executor - - helm/opensandbox - - helm/opensandbox-node-agent - - helm - version: - description: "Version to release (e.g. 1.0.5 or v0.3.0)" - required: true - type: string - from_tag: - description: "Optional previous tag override" - required: false - type: string - no_path_filter: - description: "Disable default target path filtering" - required: true - default: false - type: boolean - extra_paths: - description: "Optional extra paths (comma-separated)" - required: false - type: string - initial_release: - description: "Allow release without previous tag" - required: true - default: false - type: boolean - dry_run: - description: "Preview only, no side effects" - required: true - default: true - type: boolean - -permissions: - contents: write - id-token: write - attestations: write - artifact-metadata: write - -jobs: - release-preflight: - uses: ./.github/workflows/release-preflight.yml - with: - require_approval: ${{ inputs.dry_run == false }} - - release: - needs: release-preflight - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Ensure script executable - run: chmod +x scripts/release/create-release.sh - - - name: Run generic release script - id: release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - ARGS=( - --target "${{ inputs.target }}" - --version "${{ inputs.version }}" - ) - - if [[ -n "${{ inputs.from_tag }}" ]]; then - ARGS+=(--from-tag "${{ inputs.from_tag }}") - fi - - if [[ "${{ inputs.no_path_filter }}" == "true" ]]; then - ARGS+=(--no-path-filter) - fi - - if [[ -n "${{ inputs.extra_paths }}" ]]; then - IFS=',' read -r -a EXTRA_PATHS <<< "${{ inputs.extra_paths }}" - for path in "${EXTRA_PATHS[@]}"; do - trimmed="$(echo "$path" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - if [[ -n "$trimmed" ]]; then - ARGS+=(--path "$trimmed") - fi - done - fi - - if [[ "${{ inputs.initial_release }}" == "true" ]]; then - ARGS+=(--initial-release) - fi - - if [[ "${{ inputs.dry_run }}" == "true" ]]; then - ARGS+=(--dry-run) - fi - - scripts/release/create-release.sh "${ARGS[@]}" - - - name: Verify release tag on origin - if: ${{ inputs.dry_run == false }} - env: - RELEASE_TAG: ${{ steps.release.outputs.tag }} - run: | - set -euo pipefail - local_commit="$(git rev-parse "${RELEASE_TAG}^{commit}")" - remote_commit="$(git ls-remote origin "refs/tags/${RELEASE_TAG}^{}" | awk 'NR == 1 { print $1 }')" - - if [[ -z "$remote_commit" ]]; then - remote_commit="$(git ls-remote origin "refs/tags/${RELEASE_TAG}" | awk 'NR == 1 { print $1 }')" - fi - - if [[ -z "$remote_commit" ]]; then - echo "::error::Release tag '${RELEASE_TAG}' does not exist on origin. Have an authorized release manager push the tag before publishing source artifacts." - exit 1 - fi - - if [[ "$local_commit" != "$remote_commit" ]]; then - echo "::error::Local release tag '${RELEASE_TAG}' resolves to ${local_commit}, but origin resolves to ${remote_commit}. Refusing to publish source artifacts." - exit 1 - fi - - - name: Create release source archive - if: ${{ inputs.dry_run == false }} - id: source_archive - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ steps.release.outputs.tag }} - run: | - set -euo pipefail - SAFE_TAG="$(printf '%s' "$RELEASE_TAG" | tr '/:' '--')" - ARCHIVE_NAME="opensandbox-${SAFE_TAG}.tar.gz" - ARCHIVE_DIR="dist/release-source" - - mkdir -p "$ARCHIVE_DIR" - git archive \ - --format=tar.gz \ - --prefix="opensandbox-${SAFE_TAG}/" \ - -o "${ARCHIVE_DIR}/${ARCHIVE_NAME}" \ - "$RELEASE_TAG" - - ( - cd "$ARCHIVE_DIR" - sha256sum "$ARCHIVE_NAME" > SHA256SUMS - ) - - echo "archive_path=${ARCHIVE_DIR}/${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT" - echo "checksums_path=${ARCHIVE_DIR}/SHA256SUMS" >> "$GITHUB_OUTPUT" - - - name: Attest source release artifacts - if: ${{ inputs.dry_run == false }} - uses: actions/attest@v4 - with: - subject-path: | - ${{ steps.source_archive.outputs.archive_path }} - ${{ steps.source_archive.outputs.checksums_path }} - - - name: Upload source release artifacts - if: ${{ inputs.dry_run == false }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ steps.release.outputs.tag }} - run: | - set -euo pipefail - gh release upload "$RELEASE_TAG" \ - "${{ steps.source_archive.outputs.archive_path }}" \ - "${{ steps.source_archive.outputs.checksums_path }}" \ - --clobber diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml index 04cd46b97..22d503005 100644 --- a/.github/workflows/sdk-tests.yml +++ b/.github/workflows/sdk-tests.yml @@ -39,7 +39,7 @@ jobs: area: sdk cli-quality: - name: CLI Quality + name: CLI Quality And Tests needs: changes if: needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest @@ -72,30 +72,6 @@ jobs: run: | uv run pyright - cli-tests: - name: CLI Tests - needs: changes - if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: "latest" - - - name: Install dependencies - working-directory: cli - run: | - uv sync - - name: Run tests working-directory: cli run: | @@ -111,7 +87,7 @@ jobs: cli/reports/** python-sdk-quality: - name: Python SDK Quality (${{ matrix.package_name }}) + name: Python SDK Quality And Tests (${{ matrix.package_name }}) needs: changes if: needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest @@ -121,8 +97,10 @@ jobs: include: - package_name: sandbox package_dir: sdks/sandbox/python + coverage_target: src/opensandbox - package_name: code-interpreter package_dir: sdks/code-interpreter/python + coverage_target: src/code_interpreter steps: - name: Checkout code uses: actions/checkout@v6 @@ -158,46 +136,6 @@ jobs: run: | uv run pyright - python-sdk-tests: - name: Python SDK Tests (${{ matrix.package_name }}) - needs: changes - if: needs.changes.outputs.relevant == 'true' - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - package_name: sandbox - package_dir: sdks/sandbox/python - coverage_target: src/opensandbox - - package_name: code-interpreter - package_dir: sdks/code-interpreter/python - coverage_target: src/code_interpreter - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: "latest" - - - name: Install dependencies - working-directory: ${{ matrix.package_dir }} - run: | - uv sync - - - name: Generate API - if: matrix.package_name == 'sandbox' - working-directory: sdks/sandbox/python - run: | - uv run python scripts/generate_api.py - - name: Run tests working-directory: ${{ matrix.package_dir }} run: | @@ -281,20 +219,10 @@ jobs: ${{ matrix.package_dir }}/reports/** kotlin-sdk-quality: - name: Kotlin SDK Quality And Tests (${{ matrix.package_name }}) + name: Kotlin SDK Quality And Tests needs: changes if: needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - package_name: sandbox - package_dir: sdks/sandbox/kotlin - test_task: :sandbox:test - - package_name: code-interpreter - package_dir: sdks/sandbox/kotlin - test_task: :code-interpreter:test steps: - name: Checkout code uses: actions/checkout@v6 @@ -309,18 +237,18 @@ jobs: uses: gradle/actions/setup-gradle@v5 - name: Run quality checks and tests - working-directory: ${{ matrix.package_dir }} + working-directory: sdks/sandbox/kotlin run: | - ./gradlew spotlessCheck ${{ matrix.test_task }} + ./gradlew spotlessCheck :sandbox:test :code-interpreter:test - name: Upload Kotlin reports if: always() uses: actions/upload-artifact@v4 with: - name: kotlin-${{ matrix.package_name }}-reports + name: kotlin-reports path: | - ${{ matrix.package_dir }}/**/build/test-results/test/** - ${{ matrix.package_dir }}/**/build/reports/tests/test/** + sdks/sandbox/kotlin/**/build/test-results/test/** + sdks/sandbox/kotlin/**/build/reports/tests/test/** csharp-sdk-quality: name: C# SDK Quality And Tests (${{ matrix.package_name }}) @@ -420,9 +348,7 @@ jobs: needs: - changes - cli-quality - - cli-tests - python-sdk-quality - - python-sdk-tests - javascript-sdk-quality - kotlin-sdk-quality - csharp-sdk-quality @@ -434,9 +360,7 @@ jobs: RELEVANT: ${{ needs.changes.outputs.relevant }} CHANGES_RESULT: ${{ needs.changes.result }} CLI_QUALITY_RESULT: ${{ needs.cli-quality.result }} - CLI_TESTS_RESULT: ${{ needs.cli-tests.result }} PYTHON_QUALITY_RESULT: ${{ needs.python-sdk-quality.result }} - PYTHON_TESTS_RESULT: ${{ needs.python-sdk-tests.result }} JAVASCRIPT_RESULT: ${{ needs.javascript-sdk-quality.result }} KOTLIN_RESULT: ${{ needs.kotlin-sdk-quality.result }} CSHARP_RESULT: ${{ needs.csharp-sdk-quality.result }} @@ -447,7 +371,7 @@ jobs: exit 1 fi if [[ "$RELEVANT" == "true" ]]; then - [[ "$CLI_QUALITY_RESULT" == "success" && "$CLI_TESTS_RESULT" == "success" && "$PYTHON_QUALITY_RESULT" == "success" && "$PYTHON_TESTS_RESULT" == "success" && "$JAVASCRIPT_RESULT" == "success" && "$KOTLIN_RESULT" == "success" && "$CSHARP_RESULT" == "success" && "$GO_RESULT" == "success" ]] + [[ "$CLI_QUALITY_RESULT" == "success" && "$PYTHON_QUALITY_RESULT" == "success" && "$JAVASCRIPT_RESULT" == "success" && "$KOTLIN_RESULT" == "success" && "$CSHARP_RESULT" == "success" && "$GO_RESULT" == "success" ]] else - [[ "$RELEVANT" == "false" && "$CLI_QUALITY_RESULT" == "skipped" && "$CLI_TESTS_RESULT" == "skipped" && "$PYTHON_QUALITY_RESULT" == "skipped" && "$PYTHON_TESTS_RESULT" == "skipped" && "$JAVASCRIPT_RESULT" == "skipped" && "$KOTLIN_RESULT" == "skipped" && "$CSHARP_RESULT" == "skipped" && "$GO_RESULT" == "skipped" ]] + [[ "$RELEVANT" == "false" && "$CLI_QUALITY_RESULT" == "skipped" && "$PYTHON_QUALITY_RESULT" == "skipped" && "$JAVASCRIPT_RESULT" == "skipped" && "$KOTLIN_RESULT" == "skipped" && "$CSHARP_RESULT" == "skipped" && "$GO_RESULT" == "skipped" ]] fi diff --git a/docs/community/release-automation.md b/docs/community/release-automation.md index 96a2d80a6..9c3349832 100644 --- a/docs/community/release-automation.md +++ b/docs/community/release-automation.md @@ -231,36 +231,14 @@ If `--dry-run` is enabled, the script never creates/pushes tags and never create ## GitHub Actions Entry -You can trigger the same flow in GitHub Actions from: - -- `.github/workflows/release-generic.yml` - -Inputs exposed in the workflow dispatch form: - -- `target` -- `version` -- `from_tag` (optional) -- `initial_release` (boolean) -- `dry_run` (boolean, default `true`) - -Dry-run in GitHub Actions: - -- set `dry_run=true` -- check logs for: - - computed tag (`New tag`) - - range (`Computed range`) - - preview body (`Generated release notes preview`) - -Recommended first run in UI: - -- set `dry_run=true` -- verify the generated release notes preview in logs -- have an authorized release manager create and push the release tag from - `main` -- select that tag as the workflow ref and rerun with `dry_run=false` - -When `dry_run=false`, `.github/workflows/release-generic.yml` uploads an -explicit `opensandbox-.tar.gz` source archive and `SHA256SUMS` file to the -GitHub Release, then signs both files with GitHub/Sigstore provenance -attestations. See [Release Verification](release-verification.md) for user -verification commands and release signing coverage. +The GitHub Actions dispatch entry for this flow (`release-generic.yml`) was +removed because it had no callers; the release process uses tag pushes that +trigger the `publish-*` workflows directly. Run `scripts/release/create-release.sh` +locally to create release tags and GitHub Releases: + +When `dry_run=false`, `scripts/release/create-release.sh` creates the tag and +the GitHub Release. Source archives (`opensandbox-.tar.gz` + `SHA256SUMS`) +were previously uploaded by the removed `release-generic.yml` workflow; releases +created after its removal no longer carry source archives. See +[Release Verification](release-verification.md) for user verification commands +and release signing coverage. diff --git a/docs/community/release-verification.md b/docs/community/release-verification.md index ece39cadf..9ce4a8c48 100644 --- a/docs/community/release-verification.md +++ b/docs/community/release-verification.md @@ -91,6 +91,10 @@ Maven Central signing keys are held only in GitHub Actions secrets. ## Verify Source Releases +> Source archives are produced only for releases before 2026-08 (when the +> `release-generic.yml` workflow was removed). Newer releases have no source +> archive; skip this section for them. + Set the release tag first: ```bash diff --git a/kubernetes/go.mod b/kubernetes/go.mod index 9b104f24d..313a65b4c 100644 --- a/kubernetes/go.mod +++ b/kubernetes/go.mod @@ -87,7 +87,6 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/kubernetes/go.sum b/kubernetes/go.sum index 4d367abc2..f3303b640 100644 --- a/kubernetes/go.sum +++ b/kubernetes/go.sum @@ -148,7 +148,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=