Terraform GC provider: Conditional apply on merge vs plan on PR

Looking for the cleanest way to structure a GitHub Actions workflow for Genesys Cloud resources. The goal is to run terraform plan automatically on every pull request and only execute terraform apply when the branch merges into main. Current setup runs apply on every push, which is risky. Is there a specific action or conditional logic pattern that handles this cleanly without duplicating the job definition? Here’s the current workflow snippet:

on:
 push:
 branches:
 - main

Here is a standard pattern for splitting plan and apply logic in GitHub Actions. You don’t need to duplicate the job definition. Just use conditional triggers based on the github.event_name and github.ref.

The plan job runs on pull requests. The apply job runs only on pushes to main, but it depends on the plan job succeeding first. This ensures you never apply a broken state.

name: Genesys Cloud Terraform

on:
 push:
 branches: [ main ]
 pull_request:
 branches: [ main ]

jobs:
 plan:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v3
 - uses: hashicorp/setup-terraform@v2
 
 - name: Terraform Init
 run: terraform init
 
 - name: Terraform Plan
 run: terraform plan -out=tfplan
 env:
 GENESYS_CLOUD_ACCESS_TOKEN: ${{ secrets.GC_TOKEN }}
 
 - name: Upload Plan
 uses: actions/upload-artifact@v3
 with:
 name: tfplan
 path: tfplan

 apply:
 needs: plan
 runs-on: ubuntu-latest
 if: github.event_name == 'push' && github.ref == 'refs/heads/main'
 steps:
 - uses: actions/checkout@v3
 - uses: hashicorp/setup-terraform@v2
 
 - name: Terraform Init
 run: terraform init
 
 - name: Download Plan
 uses: actions/download-artifact@v3
 with:
 name: tfplan
 
 - name: Terraform Apply
 run: terraform apply -auto-approve tfplan
 env:
 GENESYS_CLOUD_ACCESS_TOKEN: ${{ secrets.GC_TOKEN }}

This setup keeps the workflow file clean. The apply job waits for plan to finish. It also checks if the event is a push to main. That prevents accidental applies on feature branches.

Make sure your Genesys Cloud OAuth token has the right scopes. If you’re managing integrations or users, you’ll need integration:write or user:write. Missing scopes cause silent failures in some providers.

We use this exact pattern for our New Relic instrumentation configs. It catches drift before it hits production. The artifact step saves the plan output. That way the apply job uses the exact same plan state. No re-plan needed.

One thing to watch out for. Terraform state locking. If two PRs merge at the same time, you might get a lock error. Consider using a backend with locking support like S3 with DynamoDB. Or just be careful with merge queues.

This is a Genesys Cloud deployment question, not a Studio scripting one. But since I deal with environment drift daily, here’s the pattern that actually works without duplicating jobs.

The key is using needs to chain the apply job to the plan job, then gating apply on the ref.

name: Terraform GC Deploy

on:
 pull_request:
 branches: [ main ]
 push:
 branches: [ main ]

jobs:
 plan:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v3
 - uses: hashicorp/setup-terraform@v2
 - run: terraform init
 - run: terraform plan -out=tfplan
 env:
 GC_ACCESS_TOKEN: ${{ secrets.GC_ACCESS_TOKEN }}
 - uses: actions/upload-artifact@v3
 with:
 name: tfplan
 path: tfplan

 apply:
 needs: plan
 if: github.event_name == 'push' && github.ref == 'refs/heads/main'
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v3
 - uses: hashicorp/setup-terraform@v2
 - run: terraform init
 - uses: actions/download-artifact@v3
 with:
 name: tfplan
 path: .
 - run: terraform apply -auto-approve tfplan
 env:
 GC_ACCESS_TOKEN: ${{ secrets.GC_ACCESS_TOKEN }}

The if condition on the apply job is what stops it from running on PRs. The needs: plan ensures the plan succeeded first. Upload/download the tfplan artifact so the apply job uses the exact same execution plan.

Watch out for sensitive data in the plan output if you print it to logs. The artifact approach keeps it out of the PR comments unless you explicitly format it.