chore: sync upstream main #142
Workflow file for this run
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: Terraform | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "terraform/**" | |
| - "scripts/**" | |
| - "packages/control-plane/**" | |
| - "packages/slack-bot/**" | |
| - "packages/github-bot/**" | |
| - "packages/linear-bot/**" | |
| - "packages/modal-infra/**" | |
| - "packages/daytona-infra/**" | |
| - "packages/sandbox-runtime/**" | |
| - "packages/shared/**" | |
| - "packages/web/**" # Web app deployed via Terraform when web_platform = "cloudflare" | |
| - ".github/workflows/terraform.yml" | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "terraform/**" | |
| - "scripts/**" | |
| - "packages/control-plane/**" | |
| - "packages/slack-bot/**" | |
| - "packages/github-bot/**" | |
| - "packages/linear-bot/**" | |
| - "packages/modal-infra/**" | |
| - "packages/daytona-infra/**" | |
| - "packages/sandbox-runtime/**" | |
| - "packages/shared/**" | |
| - "packages/web/**" | |
| - ".github/workflows/terraform.yml" | |
| workflow_dispatch: # Allow manual trigger | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: terraform-${{ github.ref }} | |
| cancel-in-progress: false # Don't cancel terraform operations | |
| env: | |
| TF_VERSION: "1.14.8" | |
| TF_WORKING_DIR: terraform/environments/production | |
| jobs: | |
| check-secrets: | |
| name: Check Secrets | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| outputs: | |
| has-secrets: ${{ steps.check.outputs.has-secrets }} | |
| steps: | |
| - name: Check if secrets are configured | |
| id: check | |
| run: | | |
| if [ -n "${{ secrets.CLOUDFLARE_API_TOKEN }}" ] && [ -n "${{ secrets.R2_ACCESS_KEY_ID }}" ]; then | |
| echo "has-secrets=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has-secrets=false" >> $GITHUB_OUTPUT | |
| echo "::notice::Terraform plan/apply skipped - secrets not configured. See docs/GETTING_STARTED.md for setup instructions." | |
| fi | |
| validate: | |
| name: Validate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [check-secrets] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v4 | |
| with: | |
| terraform_version: ${{ env.TF_VERSION }} | |
| - name: Terraform Format Check | |
| id: fmt | |
| run: terraform fmt -check -recursive | |
| working-directory: terraform | |
| - name: Terraform Init | |
| id: init | |
| run: | | |
| terraform init -backend=false | |
| working-directory: ${{ env.TF_WORKING_DIR }} | |
| - name: Terraform Validate | |
| id: validate | |
| run: terraform validate -no-color | |
| working-directory: ${{ env.TF_WORKING_DIR }} | |
| - name: Post Validation Results | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const hasSecrets = '${{ needs.check-secrets.outputs.has-secrets }}' === 'true'; | |
| const planNote = hasSecrets | |
| ? '' | |
| : '\n\n> **Note:** Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) for setup instructions.'; | |
| const output = `### Terraform Validation Results | |
| | Step | Status | | |
| |------|--------| | |
| | Format | ${{ steps.fmt.outcome == 'success' && '✅' || '⚠️' }} | | |
| | Init | ${{ steps.init.outcome == 'success' && '✅' || '❌' }} | | |
| | Validate | ${{ steps.validate.outcome == 'success' && '✅' || '❌' }} | | |
| ${planNote} | |
| *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; | |
| try { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: output | |
| }); | |
| } catch (error) { | |
| if (error.status === 403) { | |
| core.notice('Could not post PR comment — GITHUB_TOKEN has read-only access (expected for fork PRs).'); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| plan: | |
| name: Plan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [validate, check-secrets] | |
| if: github.event_name == 'pull_request' && needs.check-secrets.outputs.has-secrets == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build worker packages | |
| run: | | |
| npm run build -w @open-inspect/shared | |
| npm run build -w @open-inspect/control-plane | |
| npm run build -w @open-inspect/slack-bot | |
| npm run build -w @open-inspect/github-bot | |
| npm run build -w @open-inspect/linear-bot | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v4 | |
| with: | |
| terraform_version: ${{ env.TF_VERSION }} | |
| - name: Terraform Init | |
| run: | | |
| terraform init \ | |
| -backend-config="access_key=${{ secrets.R2_ACCESS_KEY_ID }}" \ | |
| -backend-config="secret_key=${{ secrets.R2_SECRET_ACCESS_KEY }}" \ | |
| -backend-config="bucket=${{ secrets.R2_BUCKET_NAME }}" \ | |
| -backend-config='endpoints={s3="https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com"}' | |
| working-directory: ${{ env.TF_WORKING_DIR }} | |
| - name: Terraform Plan | |
| id: plan | |
| run: | | |
| terraform plan -no-color -out=tfplan 2>&1 | tee plan_output.txt | |
| echo "plan<<EOF" >> $GITHUB_OUTPUT | |
| cat plan_output.txt | head -c 60000 >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| working-directory: ${{ env.TF_WORKING_DIR }} | |
| continue-on-error: true | |
| env: | |
| TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| TF_VAR_cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| TF_VAR_cloudflare_worker_subdomain: ${{ secrets.CLOUDFLARE_WORKER_SUBDOMAIN }} | |
| TF_VAR_vercel_api_token: "${{ secrets.VERCEL_API_TOKEN || '000000000000000000000000' }}" | |
| TF_VAR_vercel_team_id: "${{ secrets.VERCEL_TEAM_ID || 'unused' }}" | |
| TF_VAR_modal_token_id: ${{ secrets.MODAL_TOKEN_ID }} | |
| TF_VAR_modal_token_secret: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| TF_VAR_modal_workspace: ${{ secrets.MODAL_WORKSPACE }} | |
| TF_VAR_modal_environment: "${{ secrets.MODAL_ENVIRONMENT || 'main' }}" | |
| TF_VAR_modal_environment_web_suffix: ${{ secrets.MODAL_ENVIRONMENT_WEB_SUFFIX }} | |
| TF_VAR_github_client_id: ${{ secrets.GH_OAUTH_CLIENT_ID }} | |
| TF_VAR_github_client_secret: ${{ secrets.GH_OAUTH_CLIENT_SECRET }} | |
| TF_VAR_google_client_id: ${{ secrets.GOOGLE_CLIENT_ID }} | |
| TF_VAR_google_client_secret: ${{ secrets.GOOGLE_CLIENT_SECRET }} | |
| TF_VAR_github_app_id: ${{ secrets.GH_APP_ID }} | |
| TF_VAR_github_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| TF_VAR_github_app_installation_id: ${{ secrets.GH_APP_INSTALLATION_ID }} | |
| TF_VAR_enable_slack_bot: "${{ secrets.ENABLE_SLACK_BOT || 'true' }}" | |
| TF_VAR_slack_bot_token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| TF_VAR_slack_signing_secret: ${{ secrets.SLACK_SIGNING_SECRET }} | |
| TF_VAR_anthropic_oauth_client_id: ${{ secrets.ANTHROPIC_OAUTH_CLIENT_ID }} | |
| TF_VAR_anthropic_oauth_token_url: ${{ secrets.ANTHROPIC_OAUTH_TOKEN_URL }} | |
| TF_VAR_token_encryption_key: ${{ secrets.TOKEN_ENCRYPTION_KEY }} | |
| TF_VAR_repo_secrets_encryption_key: ${{ secrets.REPO_SECRETS_ENCRYPTION_KEY }} | |
| TF_VAR_internal_callback_secret: ${{ secrets.INTERNAL_CALLBACK_SECRET }} | |
| TF_VAR_nextauth_secret: ${{ secrets.NEXTAUTH_SECRET }} | |
| TF_VAR_modal_api_secret: ${{ secrets.MODAL_API_SECRET }} | |
| TF_VAR_allowed_users: ${{ secrets.ALLOWED_USERS }} | |
| TF_VAR_allowed_email_domains: ${{ secrets.ALLOWED_EMAIL_DOMAINS }} | |
| TF_VAR_allowed_emails: ${{ secrets.ALLOWED_EMAILS }} | |
| TF_VAR_deployment_name: ${{ secrets.DEPLOYMENT_NAME }} | |
| TF_VAR_enable_github_bot: "${{ secrets.ENABLE_GITHUB_BOT || 'false' }}" | |
| TF_VAR_github_webhook_secret: ${{ secrets.GH_WEBHOOK_SECRET }} | |
| TF_VAR_github_bot_username: ${{ secrets.GH_BOT_USERNAME }} | |
| TF_VAR_app_name: "${{ secrets.APP_NAME || 'Open-Inspect' }}" | |
| TF_VAR_app_short_name: ${{ secrets.APP_SHORT_NAME }} | |
| TF_VAR_app_icon_url: ${{ secrets.APP_ICON_URL }} | |
| TF_VAR_enable_linear_bot: "${{ secrets.ENABLE_LINEAR_BOT || 'false' }}" | |
| TF_VAR_linear_client_id: ${{ secrets.LINEAR_CLIENT_ID }} | |
| TF_VAR_linear_client_secret: ${{ secrets.LINEAR_CLIENT_SECRET }} | |
| TF_VAR_linear_webhook_secret: ${{ secrets.LINEAR_WEBHOOK_SECRET }} | |
| TF_VAR_web_platform: "${{ secrets.WEB_PLATFORM || 'vercel' }}" | |
| TF_VAR_enable_durable_object_bindings: "${{ secrets.ENABLE_DURABLE_OBJECT_BINDINGS || 'true' }}" | |
| TF_VAR_sandbox_provider: "${{ secrets.SANDBOX_PROVIDER || 'modal' }}" | |
| TF_VAR_daytona_api_url: ${{ secrets.DAYTONA_API_URL }} | |
| TF_VAR_daytona_api_key: ${{ secrets.DAYTONA_API_KEY }} | |
| TF_VAR_daytona_base_snapshot: ${{ secrets.DAYTONA_BASE_SNAPSHOT }} | |
| TF_VAR_daytona_target: ${{ secrets.DAYTONA_TARGET }} | |
| TF_VAR_vercel_sandbox_token: ${{ secrets.VERCEL_SANDBOX_TOKEN }} | |
| TF_VAR_vercel_sandbox_project_id: ${{ secrets.VERCEL_SANDBOX_PROJECT_ID }} | |
| TF_VAR_vercel_sandbox_team_id: ${{ secrets.VERCEL_SANDBOX_TEAM_ID }} | |
| TF_VAR_vercel_sandbox_api_base_url: ${{ secrets.VERCEL_SANDBOX_API_BASE_URL }} | |
| TF_VAR_vercel_base_snapshot_id: ${{ secrets.VERCEL_BASE_SNAPSHOT_ID }} | |
| TF_VAR_vercel_sandbox_runtime: "${{ secrets.VERCEL_SANDBOX_RUNTIME || 'node24' }}" | |
| TF_VAR_vercel_snapshot_expiration_ms: "${{ secrets.VERCEL_SNAPSHOT_EXPIRATION_MS || '0' }}" | |
| - name: Post Plan Results | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const planOutcome = '${{ steps.plan.outcome }}'; | |
| const plan = `${{ steps.plan.outputs.plan }}`; | |
| const truncatedPlan = plan.length > 60000 | |
| ? plan.substring(0, 60000) + '\n... (truncated)' | |
| : plan; | |
| const output = `### Terraform Plan Results | |
| **Status:** ${planOutcome === 'success' ? '✅ Success' : '❌ Failed'} | |
| <details><summary>Show Plan</summary> | |
| \`\`\`terraform | |
| ${truncatedPlan} | |
| \`\`\` | |
| </details> | |
| *Pushed by: @${{ github.actor }}*`; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: output | |
| }); | |
| - name: Plan Status | |
| if: steps.plan.outcome == 'failure' | |
| run: exit 1 | |
| apply: | |
| name: Apply | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| needs: [validate, check-secrets] | |
| if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && needs.check-secrets.outputs.has-secrets == 'true' | |
| environment: production | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build worker packages | |
| run: | | |
| npm run build -w @open-inspect/shared | |
| npm run build -w @open-inspect/control-plane | |
| npm run build -w @open-inspect/slack-bot | |
| npm run build -w @open-inspect/github-bot | |
| npm run build -w @open-inspect/linear-bot | |
| - name: Setup uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| - name: Install Modal and Python dependencies | |
| run: uv sync --frozen | |
| working-directory: packages/modal-infra | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v4 | |
| with: | |
| terraform_version: ${{ env.TF_VERSION }} | |
| - name: Terraform Init | |
| run: | | |
| terraform init \ | |
| -backend-config="access_key=${{ secrets.R2_ACCESS_KEY_ID }}" \ | |
| -backend-config="secret_key=${{ secrets.R2_SECRET_ACCESS_KEY }}" \ | |
| -backend-config="bucket=${{ secrets.R2_BUCKET_NAME }}" \ | |
| -backend-config='endpoints={s3="https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com"}' | |
| working-directory: ${{ env.TF_WORKING_DIR }} | |
| - name: Terraform Apply | |
| run: terraform apply -auto-approve | |
| working-directory: ${{ env.TF_WORKING_DIR }} | |
| env: | |
| TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| TF_VAR_cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| TF_VAR_cloudflare_worker_subdomain: ${{ secrets.CLOUDFLARE_WORKER_SUBDOMAIN }} | |
| TF_VAR_vercel_api_token: "${{ secrets.VERCEL_API_TOKEN || '000000000000000000000000' }}" | |
| TF_VAR_vercel_team_id: "${{ secrets.VERCEL_TEAM_ID || 'unused' }}" | |
| TF_VAR_modal_token_id: ${{ secrets.MODAL_TOKEN_ID }} | |
| TF_VAR_modal_token_secret: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| TF_VAR_modal_workspace: ${{ secrets.MODAL_WORKSPACE }} | |
| TF_VAR_modal_environment: "${{ secrets.MODAL_ENVIRONMENT || 'main' }}" | |
| TF_VAR_modal_environment_web_suffix: ${{ secrets.MODAL_ENVIRONMENT_WEB_SUFFIX }} | |
| TF_VAR_github_client_id: ${{ secrets.GH_OAUTH_CLIENT_ID }} | |
| TF_VAR_github_client_secret: ${{ secrets.GH_OAUTH_CLIENT_SECRET }} | |
| TF_VAR_google_client_id: ${{ secrets.GOOGLE_CLIENT_ID }} | |
| TF_VAR_google_client_secret: ${{ secrets.GOOGLE_CLIENT_SECRET }} | |
| TF_VAR_github_app_id: ${{ secrets.GH_APP_ID }} | |
| TF_VAR_github_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| TF_VAR_github_app_installation_id: ${{ secrets.GH_APP_INSTALLATION_ID }} | |
| TF_VAR_enable_slack_bot: "${{ secrets.ENABLE_SLACK_BOT || 'true' }}" | |
| TF_VAR_slack_bot_token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| TF_VAR_slack_signing_secret: ${{ secrets.SLACK_SIGNING_SECRET }} | |
| TF_VAR_anthropic_oauth_client_id: ${{ secrets.ANTHROPIC_OAUTH_CLIENT_ID }} | |
| TF_VAR_anthropic_oauth_token_url: ${{ secrets.ANTHROPIC_OAUTH_TOKEN_URL }} | |
| TF_VAR_token_encryption_key: ${{ secrets.TOKEN_ENCRYPTION_KEY }} | |
| TF_VAR_repo_secrets_encryption_key: ${{ secrets.REPO_SECRETS_ENCRYPTION_KEY }} | |
| TF_VAR_internal_callback_secret: ${{ secrets.INTERNAL_CALLBACK_SECRET }} | |
| TF_VAR_nextauth_secret: ${{ secrets.NEXTAUTH_SECRET }} | |
| TF_VAR_modal_api_secret: ${{ secrets.MODAL_API_SECRET }} | |
| TF_VAR_allowed_users: ${{ secrets.ALLOWED_USERS }} | |
| TF_VAR_allowed_email_domains: ${{ secrets.ALLOWED_EMAIL_DOMAINS }} | |
| TF_VAR_allowed_emails: ${{ secrets.ALLOWED_EMAILS }} | |
| TF_VAR_deployment_name: ${{ secrets.DEPLOYMENT_NAME }} | |
| TF_VAR_enable_github_bot: "${{ secrets.ENABLE_GITHUB_BOT || 'false' }}" | |
| TF_VAR_github_webhook_secret: ${{ secrets.GH_WEBHOOK_SECRET }} | |
| TF_VAR_github_bot_username: ${{ secrets.GH_BOT_USERNAME }} | |
| TF_VAR_app_name: "${{ secrets.APP_NAME || 'Open-Inspect' }}" | |
| TF_VAR_app_short_name: ${{ secrets.APP_SHORT_NAME }} | |
| TF_VAR_app_icon_url: ${{ secrets.APP_ICON_URL }} | |
| TF_VAR_enable_linear_bot: "${{ secrets.ENABLE_LINEAR_BOT || 'false' }}" | |
| TF_VAR_linear_client_id: ${{ secrets.LINEAR_CLIENT_ID }} | |
| TF_VAR_linear_client_secret: ${{ secrets.LINEAR_CLIENT_SECRET }} | |
| TF_VAR_linear_webhook_secret: ${{ secrets.LINEAR_WEBHOOK_SECRET }} | |
| TF_VAR_web_platform: "${{ secrets.WEB_PLATFORM || 'vercel' }}" | |
| TF_VAR_enable_durable_object_bindings: "${{ secrets.ENABLE_DURABLE_OBJECT_BINDINGS || 'true' }}" | |
| TF_VAR_sandbox_provider: "${{ secrets.SANDBOX_PROVIDER || 'modal' }}" | |
| TF_VAR_daytona_api_url: ${{ secrets.DAYTONA_API_URL }} | |
| TF_VAR_daytona_api_key: ${{ secrets.DAYTONA_API_KEY }} | |
| TF_VAR_daytona_base_snapshot: ${{ secrets.DAYTONA_BASE_SNAPSHOT }} | |
| TF_VAR_daytona_target: ${{ secrets.DAYTONA_TARGET }} | |
| TF_VAR_vercel_sandbox_token: ${{ secrets.VERCEL_SANDBOX_TOKEN }} | |
| TF_VAR_vercel_sandbox_project_id: ${{ secrets.VERCEL_SANDBOX_PROJECT_ID }} | |
| TF_VAR_vercel_sandbox_team_id: ${{ secrets.VERCEL_SANDBOX_TEAM_ID }} | |
| TF_VAR_vercel_sandbox_api_base_url: ${{ secrets.VERCEL_SANDBOX_API_BASE_URL }} | |
| TF_VAR_vercel_base_snapshot_id: ${{ secrets.VERCEL_BASE_SNAPSHOT_ID }} | |
| TF_VAR_vercel_sandbox_runtime: "${{ secrets.VERCEL_SANDBOX_RUNTIME || 'node24' }}" | |
| TF_VAR_vercel_snapshot_expiration_ms: "${{ secrets.VERCEL_SNAPSHOT_EXPIRATION_MS || '0' }}" | |
| MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} | |
| MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| - name: Post Apply Results | |
| if: always() | |
| run: | | |
| echo "### Terraform Apply Results" | |
| echo "Status: ${{ job.status }}" | |
| echo "Commit: ${{ github.sha }}" | |
| echo "Environment: production" | |
| echo "Applied by: ${{ github.actor }}" |