diff --git a/.github/workflows/build_prod_template.yml b/.github/workflows/build_prod_template.yml index ec9455b4..50971d29 100644 --- a/.github/workflows/build_prod_template.yml +++ b/.github/workflows/build_prod_template.yml @@ -4,107 +4,30 @@ on: workflow_dispatch: inputs: target_environment: - description: Target environment + description: Target environment (`all` = every production cluster) required: true type: choice - default: foxtrot + default: all options: + - all - foxtrot - - staging - juliett + - tango + - staging skip_cache: description: Skip build cache required: false type: boolean default: false -concurrency: - group: Release-${{ github.ref }}-${{ inputs.target_environment }} - cancel-in-progress: false - permissions: contents: read jobs: build-template: - name: Build E2B template - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install development dependencies - working-directory: ./template - run: pip install -r requirements-dev.txt - - - name: Resolve target environment - env: - TARGET_ENVIRONMENT: ${{ inputs.target_environment }} - FOXTROT_DOMAIN: ${{ vars.E2B_DOMAIN }} - FOXTROT_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - STAGING_API_KEY: ${{ secrets.E2B_STAGING_API_KEY }} - JULIETT_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} - run: | - set -eu - - case "$TARGET_ENVIRONMENT" in - foxtrot) - E2B_DOMAIN="$FOXTROT_DOMAIN" - E2B_API_KEY="$FOXTROT_API_KEY" - ;; - staging) - E2B_DOMAIN="e2b-staging.dev" - E2B_API_KEY="$STAGING_API_KEY" - ;; - juliett) - E2B_DOMAIN="e2b-juliett.dev" - E2B_API_KEY="$JULIETT_API_KEY" - ;; - *) - echo "Unknown target environment: $TARGET_ENVIRONMENT" >&2 - exit 1 - ;; - esac - - if [ -z "$E2B_DOMAIN" ]; then - echo "Missing E2B domain for target environment: $TARGET_ENVIRONMENT" >&2 - exit 1 - fi - - if [ -z "$E2B_API_KEY" ]; then - echo "Missing API key secret for target environment: $TARGET_ENVIRONMENT" >&2 - exit 1 - fi - - echo "::add-mask::$E2B_API_KEY" - - { - echo "E2B_DOMAIN=$E2B_DOMAIN" - echo "E2B_API_KEY=$E2B_API_KEY" - } >> "$GITHUB_ENV" - - { - echo "### Build target" - echo - echo "Target: $TARGET_ENVIRONMENT" - echo "Domain: $E2B_DOMAIN" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Build E2B template - id: build-template - working-directory: ./template - run: | - python build_prod.py - env: - SKIP_CACHE: ${{ inputs.skip_cache }} + name: Build + uses: ./.github/workflows/build_prod_template_clusters.yml + with: + target: ${{ inputs.target_environment }} + skip_cache: ${{ inputs.skip_cache }} + secrets: inherit diff --git a/.github/workflows/build_prod_template_clusters.yml b/.github/workflows/build_prod_template_clusters.yml new file mode 100644 index 00000000..8d570c97 --- /dev/null +++ b/.github/workflows/build_prod_template_clusters.yml @@ -0,0 +1,112 @@ +name: Build Prod Template (clusters) + +# Builds the production `code-interpreter-v1` template on E2B clusters. Every +# cluster is a separate tenancy with its own team, API key and template +# registry, so a template built on one cluster does not exist on the others. +# The cluster map (name, domain, API key secret) lives in the `plan` job. + +on: + workflow_call: + inputs: + target: + description: Cluster to build on, or `all` for every production cluster. + required: false + type: string + default: all + skip_cache: + description: Skip build cache + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + plan: + name: Select clusters + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.plan.outputs.matrix }} + steps: + - id: plan + env: + TARGET: ${{ inputs.target }} + run: | + MATRIX=$(jq -nc --arg target "$TARGET" ' + [ + { cluster: "foxtrot", domain: "e2b.dev", api_key_secret: "E2B_PROD_API_KEY", production: true }, + { cluster: "juliett", domain: "e2b-juliett.dev", api_key_secret: "E2B_JULIETT_API_KEY", production: true }, + { cluster: "tango", domain: "e2b-tango.dev", api_key_secret: "E2B_TANGO_API_KEY", production: true }, + { cluster: "staging", domain: "e2b-staging.dev", api_key_secret: "E2B_STAGING_API_KEY", production: false } + ] + | map(select(if $target == "all" then .production else .cluster == $target end)) + | map(del(.production)) + ') + if [ "$MATRIX" = "[]" ]; then + echo "::error::Unknown cluster: $TARGET" >&2 + exit 1 + fi + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + + build-template: + name: Build E2B template (${{ matrix.cluster }}) + needs: plan + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.plan.outputs.matrix) }} + # Builds on the same cluster serialize, across the release workflow and + # manual dispatches alike; different clusters run concurrently. + concurrency: + group: Release-${{ github.ref }}-${{ matrix.cluster }} + cancel-in-progress: false + env: + E2B_API_KEY: ${{ secrets[matrix.api_key_secret] }} + E2B_DOMAIN: ${{ matrix.domain }} + steps: + - name: Check the cluster API key + env: + CLUSTER: ${{ matrix.cluster }} + API_KEY_SECRET: ${{ matrix.api_key_secret }} + run: | + if [ -z "$E2B_API_KEY" ]; then + echo "::error::Missing secret $API_KEY_SECRET for cluster: $CLUSTER" >&2 + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Parse .tool-versions + uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install development dependencies + working-directory: ./template + run: pip install -r requirements-dev.txt + + - name: Build E2B template + working-directory: ./template + run: python build_prod.py + env: + SKIP_CACHE: ${{ inputs.skip_cache }} + + - name: Summarize + env: + CLUSTER: ${{ matrix.cluster }} + run: | + { + echo "### Build target" + echo + echo "Cluster: $CLUSTER" + echo "Domain: ${E2B_DOMAIN:-(default)}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 523d8af4..767bb350 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,10 @@ on: # makes a re-dispatch the recovery path too. workflow_dispatch: {} -concurrency: Release-${{ github.ref }}-foxtrot +# One release at a time. The per-cluster template builds serialize against +# manual `Build Prod Template` runs through their own groups, inside the +# called workflow. +concurrency: Release-${{ github.ref }} permissions: id-token: write @@ -223,46 +226,28 @@ jobs: --push \ --tag ${{ secrets.DOCKERHUB_USERNAME }}/code-interpreter:latest -f - . + # Builds the template on every production cluster; a failure on any of them + # blocks the release. build-template: name: Build E2B template - runs-on: ubuntu-latest needs: [preflight, build-docker-image] if: (!cancelled()) && !contains(needs.*.result, 'failure') && (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') - steps: - - name: Checkout repository - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install development dependencies - working-directory: ./template - run: pip install -r requirements-dev.txt - - - name: Build E2B template - id: build-template - working-directory: ./template - run: | - python build_prod.py - env: - E2B_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} + uses: ./.github/workflows/build_prod_template_clusters.yml + with: + target: all + secrets: inherit release: # Every upstream job is listed, not just the last one: a job that fails makes # its dependents *skip*, and a skipped job is not a failure — so gating on # `needs.*.result` only works for the jobs this one depends on directly. - needs: [preflight, charts-release, build-docker-image, build-template] + needs: + - preflight + - charts-release + - build-docker-image + - build-template if: (!cancelled()) && !contains(needs.*.result, 'failure') && needs.preflight.outputs.release == 'true' @@ -404,7 +389,12 @@ jobs: report-failure: # `preflight` included so a failure there is reported too, whether or not # `failure()` looks past this job's direct dependencies. - needs: [preflight, charts-release, build-docker-image, build-template, release] + needs: + - preflight + - charts-release + - build-docker-image + - build-template + - release if: failure() name: Code Interpreter Release Failed - Slack Notification runs-on: ubuntu-latest