terraform-github-actions icon indicating copy to clipboard operation
terraform-github-actions copied to clipboard

"Not applying the plan - it has changed from the plan on the PR"

Open jelder opened this issue 5 months ago • 4 comments

Problem description

I'm using tofu-plan (on the PR) and tofu-apply (at merge time), and the apply step is failing, seemingly because of whitespace differences. Note that the + is left-justified in the PR and right-justified at execution time.

Here's an excerpt from the error; full logs are included in a Gist below.

Not applying the plan - it has changed from the plan on the PR
The plan on the PR must be up to date. Alternatively, set the auto_approve input to 'true' to apply outdated plans
Performing diff between the pull request plan and the plan generated at execution time.
> are lines from the plan in the pull request
< are lines from the plan generated at execution
Plan differences:
3c3
<   + create
---
> +   create

Terraform version

OpenTofu v1.10.2

Backend

Google Cloud Storage

Workflow YAML

name: Deployment

on:
  workflow_dispatch:
  push:
    branches: [development, staging, demo, main]

env:
  ENVIRONMENT: ${{ github.ref_name == 'main' && 'production' || github.ref_name }}
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
      pull-requests: write
    environment: ${{ github.ref_name == 'main' && 'production' || github.ref_name }}
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        id: auth
        with:
          token_format: access_token
          create_credentials_file: true
          project_id: ${{ vars.GOOGLE_PROJECT_ID }}
          workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ vars.SERVICE_ACCOUNT }}

      - uses: dflook/tofu-output@v2-ghcr
        id: tf-outputs
        with:
          path: infrastructure
      - uses: dflook/tofu-apply@v2-ghcr
        id: apply
        with:
          path: infrastructure
          var_file: infrastructure/environments/${{ env.ENVIRONMENT }}.tfvars
          variables: |
            image_url = "${{ steps.tf-outputs.outputs.image_url }}"

# NOTE: the use of outputs here seemed necessary to get the image_url, which is the result of a docker build step. Open to suggestions about how this might be better achieved.

Workflow log

https://gist.github.com/jelder/e963133626a01652b2d9c25b10666fa3

Has debug logging been enabled?

  • [x] Yes, the ACTIONS_STEP_DEBUG secret was set to true when capturing the workflow log above. I understand that if I have not done this, I may not receive a response.

jelder avatar Aug 01 '25 13:08 jelder

Hi @jelder, Can you share the workflow that does the plan as well?

dflook avatar Aug 01 '25 14:08 dflook

@dflook sure!

name: PR Deployment

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [main, demo, staging, development]

env:
  DOCKER_BUILDKIT: 1
  REGISTRY: gcr.io
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    outputs:
      image_tag: ${{ steps.image_tag.outputs.image_tag }}
    steps:
      - uses: actions/checkout@v4
      - name: Set image_tag
        id: image_tag
        run: |
          repo_name=$(basename $GITHUB_REPOSITORY)
          image_tag=${REGISTRY}/${{ vars.GOOGLE_PROJECT_ID }}/${repo_name}:${{ github.sha }}
          echo "image_tag=$image_tag" >> "$GITHUB_OUTPUT"
      - uses: google-github-actions/auth@v2
        id: auth
        with:
          token_format: access_token
          create_credentials_file: true
          project_id: ${{ vars.GOOGLE_PROJECT_ID }}
          workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ vars.SERVICE_ACCOUNT }}
      - uses: docker/login-action@v3
        with:
            registry: ${{ env.REGISTRY }}
            username: oauth2accesstoken
            password: ${{ steps.auth.outputs.access_token }}
      - uses: docker/setup-buildx-action@v3
      - uses: docker/build-push-action@v6
        id: build
        with:
          context: .
          push: true
          tags: ${{ steps.image_tag.outputs.image_tag }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64
          build-args: |
            PROJECT_TAG=pr-${{ github.event.number }}
  
  tofu-plan:
    runs-on: ubuntu-latest
    needs: build
    permissions:
      contents: read
      id-token: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: dflook/tofu-validate@v2-ghcr
        with:
          path: infrastructure
      - name: Set paccurate_env
        uses: actions/github-script@v7
        with:
          script: |
            const base_ref = process.env.GITHUB_BASE_REF;
            const paccurate_env = (() => {
              switch (base_ref) {
                case "main":
                  return "production";
                case "development":
                case "demo":
                case "staging":
                  return base_ref;
              }
            })();
            console.log({ base_ref, paccurate_env });
            if (paccurate_env) {
              core.exportVariable('paccurate_env', paccurate_env);
            }
      - uses: google-github-actions/auth@v2
        id: auth
        with:
          token_format: access_token
          create_credentials_file: true
          project_id: ${{ vars.GOOGLE_PROJECT_ID }}
          workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ vars.SERVICE_ACCOUNT }}
      - uses: dflook/tofu-plan@v2-ghcr
        id: plan
        with:
          path: infrastructure
          var_file: infrastructure/environments/${{ env.paccurate_env }}.tfvars
          variables: |
            image_url = "${{ needs.build.outputs.image_tag }}"

jelder avatar Aug 01 '25 14:08 jelder

Thanks, it looks like there is an issue with the generated diff in the workflow log. This diff should just be informative - I think there is a genuine difference in the plan.

Looking at the workflow log, the PR includes a container image url of ghcr.io/... by that's missing in the plan generated by the apply step. I think the variable may not be set right.

dflook avatar Aug 01 '25 15:08 dflook

Regarding the container image, I did struggle with that aspect of this architecture. Given the choice between rebuilding the docker image, and deploying exactly the one resulting from the reviewed PR, I'd prefer the latter (deploy the PR one).

My current approach is to use dflook/tofu-output@v2-ghcr to fetch the output from the plan in the PR but that doesn't seem to be working. My guess is it doesn't do the magical "get the plan from a PR comment" thing. Curious if you have any recommendations here?

jelder avatar Aug 01 '25 15:08 jelder