Terraform/OpenTofu via Pull Request (tf-via-pr)

May 30, 2026 · View on GitHub

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

Terraform/OpenTofu via Pull Request (tf-via-pr)

What does it do?

Who is it for?

  • Plan and apply changes with CLI arguments and encrypted plan file to avoid configuration drift.
  • Outline diff within up-to-date PR comment and matrix-friendly workflow summary, complete with log.
  • DevOps and Platform engineers wanting to empower their teams to self-service scalably.
  • Maintainers looking to secure their pipeline without the overhead of containers or VMs.

View: Usage Examples · Inputs · Outputs · Security · Changelog · License

PR comment of plan output with "Diff of changes" section expanded.


Usage Examples

How to get started?

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 PR comment.

    steps:
      - uses: actions/checkout@v6

      - uses: hashicorp/setup-terraform@v4
        with:
          terraform_wrapper: false

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

Tip

  • All supported arguments (e.g., -backend-config, -destroy, -parallelism, etc.) are listed below.
  • Environment variables can be passed in for cloud platform authentication (e.g., configure-aws-credentials for short-lived credentials via OIDC).
  • Recommend setting terraform_wrapper/tofu_wrapper to false in order to output the detailed exit code for better error handling.

Where to find more examples?

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

#1 example ⤴

Runs on pull_request (plan) and push (apply) events with Terraform, AWS authentication and cache.


#2 example ⤴

Runs on pull_request (plan) and merge_group (apply) events with OpenTofu in matrix strategy.


#3 example ⤴

Runs on pull_request (plan) and push (apply) events with fmt/validate checks and TFLint.


#4 example ⤴

Runs on pull_request (plan) and push (apply) events with conditional jobs based on plan file.


#5 example ⤴

Runs on labeled and workflow_dispatch manual events on GitHub Enterprise (GHE) self-hosted runner.


#6 example ⤴

Runs on schedule cron event with -refresh-only to open an issue on configuration drift.



Are there online references?

  1. Medium - Enhance Terraform/Tofu PR Automation with GitHub Action
  2. Medium - Secure cloud provisioning pipeline with GitHub automation
  3. Medium - The Case for Terraform/Tofu Merge Queue
  4. Terraform Weekly - Issue #207
  5. Awesome OpenTofu - CI Tools
  6. LinkedIn - GitOps with Terraform

How does encryption work?

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

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

unzip <tfplan.zip>
openssl enc -d -aes-256-ctr -pbkdf2 -salt \
  -in   tfplan.encrypted \
  -out  tfplan.decrypted \
  -pass pass:"<passphrase>"
<tf.tool> show tfplan.decrypted

Inputs

All supported CLI argument inputs are listed below with accompanying options, while workflow configuration inputs are listed here.

Configuration

TypeNameDescription
CLIworking-directorySpecify the working directory of TF code, alias of arg-chdir.
Example: path/to/directory
CLIcommandCommand to run between: plan or apply.1
Example: plan
CLItoolProvisioning tool to use between: terraform or tofu.
Default: terraform
CLIplan-fileSupply existing plan file path instead of the auto-generated one.
Example: path/to/file.tfplan
CLIpr-numberSpecify PR number in case of unsupported workflow trigger.
Example: 123
CheckformatCheck format of TF code.
Default: false
CheckvalidateCheck validation of TF code.
Default: false
Checkplan-parityReplace plan file if it matches a newly-generated one to prevent stale apply.2
Default: false
Securityplan-encryptEncrypt plan file artifact with the given input.3
Example: ${{ secrets.PASSPHRASE }}
Securitypreserve-planPreserve plan file "tfplan" in the given working directory after workflow execution.
Default: false
Securityupload-planUpload plan file as GitHub workflow artifact.
Default: true
Securityretention-daysDuration after which plan file artifact will expire in days.
Example: 90
SecuritytokenSpecify a GitHub token.
Default: ${{ github.token }}
UIexpand-diffExpand the collapsible diff section.
Default: false
UIexpand-summaryExpand the collapsible summary section.
Default: false
UIcomment-prAdd a PR comment: always, on-diff, or never.4
Default: always
UIcomment-methodPR comment by: update existing comment or recreate and delete previous one.5
Default: update
UIcomment-pos-NMarkdown content to render at various positions in the PR comment.
Example: > [!NOTE]\n> Reviewed by security.
UItag-actorTag the workflow triggering actor: always, on-diff, or never.4
Default: always
UIhide-argsHide comma-separated list of CLI arguments from the command input.6
Default: detailed-exitcode,parallelism,lock,out,var=
UIshow-argsShow comma-separated list of CLI arguments in the command input.6
Default: workspace

  1. Both command: plan and command: apply include: init, fmt (with format: true), validate (with validate: true), and workspace (with arg-workspace) commands rolled into it automatically.
    To separately run checks and/or generate outputs only, command: init can be used.

  2. Originally intended for merge_group event trigger, plan-parity: true input helps to prevent stale apply within a series of workflow runs when merging multiple PRs.

  3. The secret string input for plan-encrypt can be of any length, as long as it's consistent between encryption (plan) and decryption (apply).

  4. The on-diff option is true when the exit code of the last TF command is non-zero (ensure terraform_wrapper/tofu_wrapper is set to false).

  5. 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.

    PR comment revision history comparing plan and apply outputs.

  6. It can be desirable to hide certain arguments from the last run command input to prevent exposure in the PR comment (e.g., sensitive arg-var values). Conversely, it can be desirable to show other arguments even if they are not in last run command input (e.g., arg-workspace or arg-backend-config selection).

Arguments

Note

  • Arguments are passed to the appropriate TF command(s) automatically, whether that's fmt, init, validate, plan, or apply.
  • For repeated arguments like arg-var, arg-var-file, arg-backend-config, arg-replace and arg-target, use commas to separate multiple values (e.g., arg-var: key1=value1,key2=value2).

Applicable to respective "plan" and "apply" command inputs (including "init").

NameCLI Argument
arg-auto-approve-auto-approve
arg-backend-config-backend-config
arg-backend-backend
arg-backup-backup
arg-chdir-chdir
Alias: working-directory
arg-compact-warnings-compact-warnings
arg-concise-concise
arg-destroy-destroy
arg-detailed-exitcode-detailed-exitcode
Default: true
arg-force-copy-force-copy
arg-from-module-from-module
arg-generate-config-out-generate-config-out
arg-get-get
arg-lock-timeout-lock-timeout
arg-lock-lock
arg-lockfile-lockfile
arg-migrate-state-migrate-state
arg-parallelism-parallelism
arg-plugin-dir-plugin-dir
arg-reconfigure-reconfigure
arg-refresh-only-refresh-only
arg-refresh-refresh
arg-replace-replace
arg-state-out-state-out
arg-state-state
arg-target-target
arg-upgrade-upgrade
arg-var-file-var-file
arg-var-var
arg-workspace-workspace
Alias: TF_WORKSPACE

Applicable only when format: true.

NameCLI Argument
arg-check-check
Default: true
arg-diff-diff
Default: true
arg-list-list
arg-recursive-recursive
Default: true
arg-write-write

Applicable only when validate: true.

NameCLI Argument
arg-no-tests-no-tests
arg-test-directory-test-directory

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-bodyBody of the PR comment.
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.

Security

View security reporting policy. This project aims to be secure by default, and it should be complemented with one's own review to ensure it meets security requirements.

Tip


Changelog

View all notable changes to this project in Keep a Changelog format.

Tip

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


To-Do

  • Handling of inputs which contain space(s) (e.g., working-directory: path to/directory).
  • Handling of comma-separated inputs which contain comma(s) (e.g., arg-var: token=1,2,3); workaround with TF_CLI_ARGS environment variable.
  • Handling of interim build artifact(s) between plan and apply commands (e.g., zip archive); workaround with arg-auto-approve: true so that apply rebuilds artifact(s) for provisioning (join discussion).


tf-via-pr © 2016–present by OP5.dev.