https://github.com/op5dev/tf-via-pr
Plan and apply Terraform/OpenTofu via PR automation, using best practices for secure and scalable IaC workflows.
https://github.com/op5dev/tf-via-pr
automation aws cicd-pipeline devops github-actions infrastructure-as-code opentofu platform-engineering terraform
Last synced: 19 days ago
JSON representation
Plan and apply Terraform/OpenTofu via PR automation, using best practices for secure and scalable IaC workflows.
- Host: GitHub
- URL: https://github.com/op5dev/tf-via-pr
- Owner: OP5dev
- License: apache-2.0
- Created: 2023-02-07T00:30:15.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2025-03-30T20:47:19.000Z (23 days ago)
- Last Synced: 2025-04-03T15:18:03.175Z (20 days ago)
- Topics: automation, aws, cicd-pipeline, devops, github-actions, infrastructure-as-code, opentofu, platform-engineering, terraform
- Language: Shell
- Homepage: https://OP5.dev/s/tf-via-pr
- Size: 31.5 MB
- Stars: 189
- Watchers: 3
- Forks: 23
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
[](https://github.com/hashicorp/setup-terraform "Terraform Compatible.")
[](https://github.com/opentofu/setup-opentofu "OpenTofu Compatible.")
*
[](LICENSE "Apache License 2.0.")
[](https://github.com/op5dev/tf-via-pr/releases "View all releases.")
*
[](https://github.com/op5dev/tf-via-pr "Become a stargazer.")# 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](#usage-examples) · [Inputs](#inputs) · [Outputs](#outputs) · [Security](#security) · [Changelog](#changelog) · [License](#license)
[](https://raw.githubusercontent.com/op5dev/tf-via-pr/refs/heads/main/.github/assets/comment.png "View full-size image.")
## Usage Examples
### How to get started?
```yaml
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@v4
- uses: hashicorp/setup-terraform@v3
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](#arguments).
> - Environment variables can be passed in for cloud platform authentication (e.g., [configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials "Configuring AWS credentials for use in GitHub Actions.") for short-lived credentials via OIDC).
> - Recommend setting `terraform_wrapper`/`tofu_wrapper` to `false` in order to output the [detailed exit code](https://developer.hashicorp.com/terraform/cli/commands/plan#detailed-exitcode) for better error handling.
### Where to find more examples?
The following workflows showcase common use cases, while a comprehensive list of inputs is [documented](#inputs) 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.
### 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](https://docs.openssl.org/master/man1/openssl-enc/ "OpenSSL encryption documentation.")'s symmetric stream counter mode ([256 bit AES in CTR](https://docs.openssl.org/3.3/man1/openssl-enc/#supported-ciphers:~:text=192/256%20bit-,AES%20in%20CTR%20mode,-aes%2D%5B128%7C192)) 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):
```fish
unzip
openssl enc -d -aes-256-ctr -pbkdf2 -salt \
-in tfplan.encrypted \
-out tfplan.decrypted \
-pass pass:""
show tfplan.decrypted
```
## Inputs
All supported CLI argument inputs are [listed below](#arguments) with accompanying options, while workflow configuration inputs are listed here.
### Configuration
| Type | Name | Description |
| -------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| CLI | `working-directory` | Specify the working directory of TF code, alias of `arg-chdir`.Example: `path/to/directory` |
| CLI | `command` | Command to run between: `plan` or `apply`.1Example: `plan` |
| CLI | `tool` | Provisioning tool to use between: `terraform` or `tofu`.Default: `terraform` |
| CLI | `plan-file` | Supply existing plan file path instead of the auto-generated one.Example: `path/to/file.tfplan` |
| Check | `format` | Check format of TF code.Default: `false` |
| Check | `validate` | Check validation of TF code.Default: `false` |
| Check | `plan-parity` | Replace plan file if it matches a newly-generated one to prevent stale apply.2Default: `false` |
| Security | `plan-encrypt` | Encrypt plan file artifact with the given input.3Example: `${{ secrets.PASSPHRASE }}` |
| Security | `preserve-plan` | Preserve plan file "tfplan" in the given working directory after workflow execution.Default: `false` |
| Security | `upload-plan` | Upload plan file as GitHub workflow artifact.Default: `true` |
| Security | `retention-days` | Duration after which plan file artifact will expire in days.Example: `90` |
| Security | `token` | Specify a GitHub token.Default: `${{ github.token }}` |
| UI | `label-pr` | Add a PR label with the command input (e.g., `tf:plan`).Default: `true` |
| UI | `comment-pr` | Add a PR comment: `always`, `on-change`, or `never`.4Default: `always` |
| UI | `comment-method` | PR comment by: `update` existing comment or `recreate` and delete previous one.5Default: `update` |
| UI | `tag-actor` | Tag the workflow triggering actor: `always`, `on-change`, or `never`.4Default: `always` |
| UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6Default: `detailed-exitcode,parallelism,lock,out,var=` |
| UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6Default: `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.
1. 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.
1. The secret string input for `plan-encrypt` can be of any length, as long as it's consistent between encryption (plan) and decryption (apply).
1. The `on-change` option is true when the exit code of the last TF command is non-zero (ensure `terraform_wrapper`/`tofu_wrapper` is set to `false`).
1. 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.
[](https://raw.githubusercontent.com/op5dev/tf-via-pr/refs/heads/main/.github/assets/revisions.png "View full-size image.")
1. 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 ("init" included).
| Name | CLI 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`.
| Name | CLI 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`.
| Name | CLI Argument |
| -------------------- | ----------------- |
| `arg-no-tests` | `-no-tests` |
| `arg-test-directory` | `-test-directory` |
## Outputs
| Type | Name | Description |
| -------- | -------------- | --------------------------------------------- |
| Artifact | `plan-id` | ID of the plan file artifact. |
| Artifact | `plan-url` | URL of the plan file artifact. |
| CLI | `command` | Input of the last TF command. |
| CLI | `diff` | Diff of changes, if present (truncated). |
| CLI | `exitcode` | Exit code of the last TF command. |
| CLI | `result` | Result of the last TF command (truncated). |
| CLI | `summary` | Summary of the last TF command. |
| Workflow | `check-id` | ID of the check run. |
| Workflow | `comment-body` | Body of the PR comment. |
| Workflow | `comment-id` | ID of the PR comment. |
| Workflow | `job-id` | ID of the workflow job. |
| Workflow | `run-url` | URL of the workflow run. |
| Workflow | `identifier` | Unique name of the workflow run and artifact. |
## Security
View [security policy and reporting instructions](SECURITY.md).
> [!TIP]
>
> Pin your workflow version to a specific release tag or SHA to harden your CI/CD pipeline security against supply chain attacks.
## Changelog
View [all notable changes](https://github.com/op5dev/tf-via-pr/releases "Releases.") to this project in [Keep a Changelog](https://keepachangelog.com "Keep a Changelog.") format, which adheres to [Semantic Versioning](https://semver.org "Semantic Versioning.").
> [!TIP]
>
> All forms of **contribution are very welcome** and deeply appreciated for fostering open-source projects.
>
> - [Create a PR](https://github.com/op5dev/tf-via-pr/pulls "Create a pull request.") to contribute changes you'd like to see.
> - [Raise an issue](https://github.com/op5dev/tf-via-pr/issues "Raise an issue.") to propose changes or report unexpected behavior.
> - [Open a discussion](https://github.com/op5dev/tf-via-pr/discussions "Open a discussion.") to discuss broader topics or questions.
> - [Become a stargazer](https://github.com/op5dev/tf-via-pr/stargazers "Become a stargazer.") if you find this project useful.
### 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`)—use `TF_CLI_ARGS` [workaround](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_cli_args-and-tf_cli_args_name).
## License
- This project is licensed under the permissive [Apache License 2.0](LICENSE "Apache License 2.0.").
- All works herein are my own, shared of my own volition, and [contributors](https://github.com/op5dev/tf-via-pr/graphs/contributors "Contributors.").
- Copyright 2016-present [Rishav Dhar](https://github.com/rdhar "Rishav Dhar's GitHub profile.") — All wrongs reserved.