fix: case-insensitive actor and label conditions (#589) #1247
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='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 || 'unused' }}" | |
| 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_github_client_id: ${{ secrets.GH_OAUTH_CLIENT_ID }} | |
| TF_VAR_github_client_secret: ${{ secrets.GH_OAUTH_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_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| 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_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_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 }} | |
| - 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: 20 | |
| 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@v6 | |
| - 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='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 || 'unused' }}" | |
| 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_github_client_id: ${{ secrets.GH_OAUTH_CLIENT_ID }} | |
| TF_VAR_github_client_secret: ${{ secrets.GH_OAUTH_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_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| 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_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_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 }} | |
| 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 }}" |