Home

Awesome

Terraform Compatible OpenTofu Compatible * GitHub license GitHub release tag * GitHub repository stargazers

Terraform/OpenTofu via Pull Request (TF-via-PR)

<table> <tr> <th> <h3>What does it do?</h3> </th> <th> <h3>Who is it for?</h3> </th> </tr> <tr> <td> <ul> <li>Plan and apply changes with CLI arguments and <strong>encrypted plan file</strong> to avoid configuration drift.</li> <li>Outline diff changes within updated <strong>PR comment</strong> and matrix-friendly workflow summary, complete with log.</li> </ul> </td> <td> <ul> <li>DevOps and Platform engineers wanting to empower their teams to <strong>self-service</strong> scalably.</li> <li>Maintainers looking to <strong>secure</strong> their pipeline without the overhead of containers or VMs.</li> </ul> </td> </tr> </table> </br>

View: Usage Examples · In/Output Parameters · Security · Changelog · License

PR comment of plan output with "Diff of changes" section expanded. </br></br>

Usage

How to get started quickly?

on:
  pull_request:
  push:
    branches: [main]

jobs:
  provision:
    runs-on: ubuntu-latest

    permissions:
      actions: read        # Required to identify workflow run.
      checks: write        # Required to add status summary.
      contents: read       # Required to checkout repository.
      pull-requests: write # Required to add comment and label.

    steps:
      - uses: actions/checkout@4
      - uses: hashicorp/setup-terraform@v3
      - uses: op5dev/tf-via-pr@v13
        with:
          # Run plan by default, or apply with lock on merge.
          command: ${{ github.event_name == 'push' && 'apply' || 'plan' }}
          arg-lock: ${{ github.event_name == 'push' }}
          arg-var-file: env/dev.tfvars
          arg-workspace: dev-use1
          working-directory: path/to/directory
          plan-encrypt: ${{ secrets.PASSPHRASE }}

[!TIP]

Where to find more examples?

The following workflows showcase common use cases, while a comprehensive list of inputs is documented below.

<table> <tr> <td> </br> <a href="/.github/examples/pr_push_auth.yaml"><strong>Run on</strong></a> <code>pull_request</code> (plan) and <code>push</code> (apply) events with Terraform, AWS <strong>authentication</strong> and <strong>caching</strong>. </br></br> </td> <td> </br> <a href="/.github/examples/pr_merge_matrix.yaml"><strong>Run on</strong></a> <code>pull_request</code> (plan) and <code>merge_group</code> (apply) events with OpenTofu in <strong>matrix</strong> strategy. </br></br> </td> </tr> <tr> <td> </br> <a href="/.github/examples/pr_push_stages.yaml"><strong>Run on</strong></a> <code>pull_request</code> (plan) and <code>push</code> (apply) events with <strong>conditional job stages</strong> based on plan file. </br></br> </td> <td> </br> <a href="/.github/examples/schedule_refresh.yaml"><strong>Run on</strong></a> <code>schedule</code> (cron) event with <code>-refresh-only</code> to open an issue on <strong>configuration drift</strong>. </br></br> </td> </tr> <tr> <td> </br> <a href="/.github/examples/pr_push_lint.yaml"><strong>Run on</strong></a> <code>pull_request</code> (plan) and <code>push</code> (apply) events with <strong>fmt/validate checks</strong> and TFLint. </br></br> </td> <td> </br> <a href="/.github/examples/pr_self_hosted.yaml"><strong>Run on</strong></a> <code>pull_request</code> (plan or apply) event with Terraform and OpenTofu on <strong>self-hosted</strong> runner. </br></br> </td> </tr> </table> </br>

How does encryption work?

Before the workflow uploads the plan file as an artifact, it can be encrypted with a passphrase (e.g., ${{ secrets.PASSPHRASE }}) to prevent exposure of sensitive data using plan-encrypt input. This is done with OpenSSL's symmetric stream counter mode encryption with salt and pbkdf2.

In order to decrypt the plan file locally, use the following commands after downloading the artifact (noting the whitespace before openssl to prevent recording the command in shell history):

unzip <tf.plan>
 openssl enc -aes-256-ctr -pbkdf2 -salt -in <tf.plan> -out tf.plan.decrypted -pass pass:"<passphrase>" -d
<tf.tool> show tf.plan.decrypted
</br>

For each workflow run, a matrix-friendly job summary with logs is added as a fallback to the PR comment. Below this, you'll find a list of plan file artifacts generated during runtime.</br>

Workflow job summary with plan file artifact. </br></br>

Parameters

Inputs - Configuration

TypeNameDescription
CLIcommandCommand to run between: plan or apply. Optionally init for checks and outputs only.</br>Example: plan
CLIworking-directorySpecify the working directory of TF code, alias of arg-chdir.</br>Example: path/to/directory
CLItoolProvisioning tool to use between: terraform or tofu.</br>Default: terraform
CheckformatCheck format of TF code.</br>Default: false
CheckvalidateCheck validation of TF code.</br>Default: false
Checkplan-parityReplace the plan file if it matches a newly-generated one to prevent stale apply (very rarely needed nowadays).</br>Default: false
Securityplan-encryptEncrypt plan file artifact with the given input.</br>Example: ${{ secrets.PASSPHRASE }}
SecuritytokenSpecify a GitHub token.</br>Default: ${{ github.token }}
UIlabel-prAdd a PR label with the command input.</br>Default: true
UIcomment-prAdd a PR comment: always, on-change, or never.</br>Default: always
UIcomment-methodPR comment by: update existing comment or recreate and delete previous one.</br>Default: update
UItag-actorTag the workflow triggering actor: always, on-change, or never.</br>Default: always
UIhide-argsHide comma-separated list of CLI arguments from the command input.</br>Default: detailed-exitcode,lock,out,var=
UIshow-argsShow comma-separated list of CLI arguments in the command input.</br>Default: workspace
</br>

The default behavior of comment-method is to update the existing PR comment with the latest plan/apply output, making it easy to track changes over time through the comment's revision history.</br>

PR comment revision history comparing plan and apply outputs. </br></br>

Inputs - Arguments

[!NOTE]

<details><summary>Toggle view of all available CLI arguments.</summary>
NameCLI Argument
arg-auto-approve-auto-approve
arg-backend-config-backend-config
arg-backend-backend
arg-backup-backup
arg-chdir-chdir
arg-check-check</br>Default: true
arg-compact-warnings-compact-warnings
arg-concise-concise
arg-destroy-destroy
arg-detailed-exitcode-detailed-exitcode</br>Default: true
arg-diff-diff</br>Default: true
arg-force-copy-force-copy
arg-from-module-from-module
arg-generate-config-out-generate-config-out
arg-get-get
arg-list-list
arg-lock-timeout-lock-timeout
arg-lock-lock
arg-lockfile-lockfile
arg-migrate-state-migrate-state
arg-no-tests-no-tests
arg-or-create-or-create</br>Default: true
arg-parallelism-parallelism
arg-plugin-dir-plugin-dir
arg-reconfigure-reconfigure
arg-recursive-recursive</br>Default: true
arg-refresh-only-refresh-only
arg-refresh-refresh
arg-replace-replace
arg-state-out-state-out
arg-state-state
arg-target-target
arg-test-directory-test-directory
arg-upgrade-upgrade
arg-var-file-var-file
arg-var-var
arg-workspace-workspace
arg-write-write
</details> </br>

Outputs

TypeNameDescription
Artifactplan-idID of the plan file artifact.
Artifactplan-urlURL of the plan file artifact.
CLIcommandInput of the last TF command.
CLIdiffDiff of changes, if present (truncated).
CLIexitcodeExit code of the last TF command.
CLIresultResult of the last TF command (truncated).
CLIsummarySummary of the last TF command.
Workflowcheck-idID of the check run.
Workflowcomment-idID of the PR comment.
Workflowjob-idID of the workflow job.
Workflowrun-urlURL of the workflow run.
WorkflowidentifierUnique name of the workflow run and artifact.
</br>

Security

View security policy and reporting instructions.

[!TIP]

Pin your workflow version to a specific release tag or SHA to harden your CI/CD pipeline security against supply chain attacks.

</br>

Changelog

View all notable changes to this project in Keep a Changelog format, which adheres to Semantic Versioning.

[!TIP]

All forms of contribution are very welcome and deeply appreciated for fostering open-source projects.

To-Do

License