Skip to content

fix: case-insensitive actor and label conditions (#589) #1247

fix: case-insensitive actor and label conditions (#589)

fix: case-insensitive actor and label conditions (#589) #1247

Workflow file for this run

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 }}"