diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..dec7810d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +## v0.5.0 + +### Added + +* Added new YAML syntax for GitHub Actions. + +### Changed + +* Completely refactored the codebase into one GitHub Action. Please refer to the README for current usage. + +### Deprecated + +N/A + +### Removed + +* Removed all `TF_ACTION` environment variables. Please refer to the README for current usage. +* Removed HashiCorp Configuration Language (HCL) syntax. + +### Fixed + +* The actions now use the new YAML syntax. ([#67](https://github.com/hashicorp/terraform-github-actions/issues/67)) +* Added support for Terraform 0.11.14. ([#42](https://github.com/hashicorp/terraform-github-actions/issues/67)) +* Comments will not be posted to pull requests when `terraform plan` contains no changes. ([#29](https://github.com/hashicorp/terraform-github-actions/issues/67)) +* Added ability to specify a Terraform version to use. ([#23](https://github.com/hashicorp/terraform-github-actions/issues/67)) + +### Security + +N/A diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a336aa76 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine:3 + +RUN ["/bin/sh", "-c", "apk add --update --no-cache bash ca-certificates curl git jq openssh"] + +RUN ["bin/sh", "-c", "mkdir -p /src"] + +COPY ["src", "/src/"] + +ENTRYPOINT ["/src/main.sh"] diff --git a/README.md b/README.md index 4bfb980c..e1d5a31c 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,50 @@ # Terraform GitHub Actions -These official Terraform GitHub Actions allow you to run `terraform fmt`, `validate`, `plan` and `apply` on your pull requests to help you review, validate and apply Terraform changes. -## Getting Started -To get started, check out our documentation: [https://www.terraform.io/docs/github-actions/getting-started/](https://www.terraform.io/docs/github-actions/getting-started/). +Terraform GitHub Actions allow you to run Terraform commands within GitHub Actions. -## Actions +The output of the actions can be viewed from the Actions tab in the main repository view. If the actions are executed on a `pull_request` event, a comment may be posted on the pull request. -### Fmt Action -Runs `terraform fmt` and comments back if any files are not formatted correctly. -Terraform Fmt Action +## Success Criteria -### Validate Action -Runs `terraform validate` and comments back on error. -Terraform Validate Action +An exit code of `0` is considered a successful execution. -### Plan Action -Runs `terraform plan` and comments back with the output. -Terraform Plan Action +## Usage -### Apply Action -Runs `terraform apply` and comments back with the output. -Terraform Apply Action \ No newline at end of file +Please refer to the examples within the `examples` directory for usage. + +## Inputs + +| Name | Required | Default | Description | +|--------------------------|----------|---------|---------------------------------------------| +| `tf_actions_version` | `true` | | Terraform version to install. | +| `tf_actions_subcommand` | `true` | | Terraform subcommand to execute. | +| `tf_actions_working_dir` | `false` | `.` | Terraform working directory. | +| `tf_actions_comment` | `false` | `true` | Whether or not to comment on pull requests. | + +## Outputs + +| Name | Description | +|-------------------------------|------------------------------------------------------| +| `tf_actions_plan_has_changes` | Whether or not the Terraform plan contained changes. | + +## Secrets + +| Name | Description | +|--------------------------|----------------------------------------------------------------------------------------------------------------------| +| `GITHUB_TOKEN` | The GitHub API token used to post comments to pull requests. Not required if `tf_actions_comment` is set to `false`. | + +Other secrets may be needed to authenticate with Terraform backends and providers. + +**WARNING:** These secrets could be exposed if the action is executed on a malicious Terraform file. To avoid this, it is recommended to not use this action on public repos or repos where untrusted users can submit pull requests. + +## Environment Variables + +The usual [Terraform environment variables](https://www.terraform.io/docs/commands/environment-variables.html) are supported. Here are the environments variables that might be the most beneficial. + +* [`TF_LOG`](https://www.terraform.io/docs/commands/environment-variables.html#tf_log) +* [`TF_VAR_name`](https://www.terraform.io/docs/commands/environment-variables.html#tf_var_name) +* [`TF_CLI_ARGS`](https://www.terraform.io/docs/commands/environment-variables.html#tf_cli_args-and-tf_cli_args_name) +* [`TF_CLI_ARGS_name`](https://www.terraform.io/docs/commands/environment-variables.html#tf_cli_args-and-tf_cli_args_name) +* `TF_WORKSPACE` + +Other environment variables may be configured to pass data into Terraform backends and providers. If the data is sensitive, consider using [secrets](#secrets) instead. diff --git a/action.yml b/action.yml new file mode 100644 index 00000000..dd5183a8 --- /dev/null +++ b/action.yml @@ -0,0 +1,25 @@ +name: 'Terraform GitHub Actions' +description: 'Runs Terraform commands via GitHub Actions.' +author: 'HashiCorp, Inc. Terraform Team ' +branding: + icon: 'terminal' + color: 'purple' +inputs: + tf_actions_version: + description: 'Terraform version to install.' + required: true + tf_actions_subcommand: + description: 'Terraform subcommand to execute.' + required: true + tf_actions_working_dir: + description: 'Terraform working directory.' + default: '.' + tf_actions_comment: + description: 'Whether or not to comment on pull requests.' + default: true +outputs: + tf_actions_plan_has_changes: + description: 'Whether or not the Terraform plan contained changes.' +runs: + using: 'docker' + image: './Dockerfile' diff --git a/apply/Dockerfile b/apply/Dockerfile deleted file mode 100644 index f4d7989d..00000000 --- a/apply/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM hashicorp/terraform:0.12.12 - -LABEL "com.github.actions.name"="terraform apply" -LABEL "com.github.actions.description"="Run Terraform Apply" -LABEL "com.github.actions.icon"="play-circle" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="https://github.com/hashicorp/terraform-github-actions" -LABEL "homepage"="http://github.com/hashicorp/terraform-github-actions" -LABEL "maintainer"="HashiCorp Terraform Team " - -RUN apk --update --no-cache add jq curl bash - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/apply/README.md b/apply/README.md deleted file mode 100644 index f67fdfe3..00000000 --- a/apply/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Terraform Apply Action -Runs `terraform apply` and comments back on the pull request with the apply output. - -See [https://www.terraform.io/docs/github-actions/actions/apply.html](https://www.terraform.io/docs/github-actions/actions/apply.html). diff --git a/apply/entrypoint.sh b/apply/entrypoint.sh deleted file mode 100755 index 44df24a2..00000000 --- a/apply/entrypoint.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/sh - -# stripcolors takes some output and removes ANSI color codes. -stripcolors() { - echo "$1" | sed 's/\x1b\[[0-9;]*m//g' -} - -# wrap takes some output and wraps it in a collapsible markdown section if -# it's over $TF_ACTION_WRAP_LINES long. -wrap() { - if [[ $(echo "$1" | wc -l) -gt ${TF_ACTION_WRAP_LINES:-20} ]]; then - echo " -
Show Output - -\`\`\` -$1 -\`\`\` - -
-" -else - echo " -\`\`\` -$1 -\`\`\` -" -fi -} - -set -e - -cd "${TF_ACTION_WORKING_DIR:-.}" - -if [[ ! -z "$TF_ACTION_TFE_TOKEN" ]]; then - cat > ~/.terraformrc << EOF -credentials "${TF_ACTION_TFE_HOSTNAME:-app.terraform.io}" { - token = "$TF_ACTION_TFE_TOKEN" -} -EOF -fi - -if [[ ! -z "$TF_ACTION_WORKSPACE" ]] && [[ "$TF_ACTION_WORKSPACE" != "default" ]]; then - terraform workspace select "$TF_ACTION_WORKSPACE" -fi - -set +e -OUTPUT=$(sh -c "TF_IN_AUTOMATION=true terraform apply -auto-approve -input=false $*" 2>&1) -SUCCESS=$? -echo "$OUTPUT" -set -e - -# If PR_DATA is null, then this is not a pull request event and so there's -# no where to comment. -PR_DATA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request) -if [ "$TF_ACTION_COMMENT" = "1" ] || [ "$TF_ACTION_COMMENT" = "false" ] || [ "$PR_DATA" = "null" ]; then - exit $SUCCESS -fi - -if [[ "$GITHUB_EVENT_NAME" == 'pull_request' ]]; then - # Build the comment we'll post to the PR. - OUTPUT=$(stripcolors "$OUTPUT") - COMMENT="" - if [ $SUCCESS -ne 0 ]; then - OUTPUT=$(wrap "$OUTPUT") - COMMENT="#### \`terraform apply\` Failed for \`$TF_ACTION_WORKING_DIRECTORY\` -$OUTPUT - -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`*" - else - # Call wrap to optionally wrap our output in a collapsible markdown section. - OUTPUT=$(wrap "$OUTPUT") - COMMENT="#### \`terraform apply\` Success for \`$TF_ACTION_WORKING_DIRECTORY\` -$OUTPUT - -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`*" - fi - - # Post the comment. - PAYLOAD=$(echo '{}' | jq --arg body "$COMMENT" '.body = $body') - COMMENTS_URL=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.comments_url) - - curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data "$PAYLOAD" "$COMMENTS_URL" > /dev/null -fi - -exit $SUCCESS diff --git a/assets/apply.png b/assets/apply.png deleted file mode 100644 index f94c26e5..00000000 Binary files a/assets/apply.png and /dev/null differ diff --git a/assets/fmt.png b/assets/fmt.png deleted file mode 100644 index 3a50b4e5..00000000 Binary files a/assets/fmt.png and /dev/null differ diff --git a/assets/plan.png b/assets/plan.png deleted file mode 100644 index a013ea84..00000000 Binary files a/assets/plan.png and /dev/null differ diff --git a/assets/validate.png b/assets/validate.png deleted file mode 100644 index dc7d8d28..00000000 Binary files a/assets/validate.png and /dev/null differ diff --git a/base-branch-filter/Dockerfile b/base-branch-filter/Dockerfile deleted file mode 100644 index e12d8fb0..00000000 --- a/base-branch-filter/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine:latest - -LABEL "com.github.actions.name"="base branch filter" -LABEL "com.github.actions.description"="Filters pull request events based on their base branch." -LABEL "com.github.actions.icon"="filter" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="https://github.com/hashicorp/terraform-github-actions" -LABEL "homepage"="http://github.com/hashicorp/terraform-github-actions" -LABEL "maintainer"="HashiCorp Terraform Team " - -RUN apk --no-cache add jq - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/base-branch-filter/README.md b/base-branch-filter/README.md deleted file mode 100644 index df68cef1..00000000 --- a/base-branch-filter/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Base Branch Filter -Filters pull request events depending on the base branch -The base branch is the branch that the pull request will be merged into. - -To use, set `args` to a regular expression that -will be matched against the destination branch. - -Note: This action only works on pull request events. - -## Example -Filter on pull requests that have been merged into `master`: -```hcl -workflow "example" { - resolves = "base-branch-filter" - # Must be used on pull_request events. - on = "pull_request" -} - -# First we use another filter to filter to only merged events. -action "merged-prs-filter" { - uses = "actions/bin/filter@master" - args = "merged true" -} - -# Then we use this filter to ensure the branch matches "master". -action "base-branch-filter" { - uses = "hashicorp/terraform-github-actions/base-branch-filter@master" - # We set args to our regex. - args = "^master$" - needs = "merged-prs-filter" -} -``` diff --git a/base-branch-filter/entrypoint.sh b/base-branch-filter/entrypoint.sh deleted file mode 100755 index fdd21363..00000000 --- a/base-branch-filter/entrypoint.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -regex="$*" -base_branch=$(jq -r .pull_request.base.ref "$GITHUB_EVENT_PATH") - -echo "$base_branch" | grep -Eq "$regex" || { - echo "base branch \"$base_branch\" does not match \"$regex\"" - exit 78 -} diff --git a/examples/pull_request_no_working_dir/.github/workflows/example.yml b/examples/pull_request_no_working_dir/.github/workflows/example.yml new file mode 100644 index 00000000..fe00e323 --- /dev/null +++ b/examples/pull_request_no_working_dir/.github/workflows/example.yml @@ -0,0 +1,43 @@ +# An example workflow using local Terraform state with no working directory set. +name: 'Terraform Workflow' +on: + - pull_request +jobs: + root: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/pull_request_no_working_dir/main.tf b/examples/pull_request_no_working_dir/main.tf new file mode 100644 index 00000000..65507d56 --- /dev/null +++ b/examples/pull_request_no_working_dir/main.tf @@ -0,0 +1,5 @@ +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/pull_request_remote_backend_name/.github/workflows/example.yml b/examples/pull_request_remote_backend_name/.github/workflows/example.yml new file mode 100644 index 00000000..5ac3840e --- /dev/null +++ b/examples/pull_request_remote_backend_name/.github/workflows/example.yml @@ -0,0 +1,46 @@ +# An example workflow using the `remote` backend with `name`. +name: 'Terraform Workflow' +on: + - pull_request +jobs: + remote: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_comment: true + env: + # TF_CLI_ARGS_init is used to pass the backend configuration to the run. + # This way is chosen to replicate how one would use the `remote` backend with Terraform Cloud/Enterprise via automation. + TF_CLI_ARGS_init: '-backend-config="token=${{ secrets.TFP_TOKEN }}" -backend-config="organization=CHANGE_ME"' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/pull_request_remote_backend_name/main.tf b/examples/pull_request_remote_backend_name/main.tf new file mode 100644 index 00000000..367ccf6c --- /dev/null +++ b/examples/pull_request_remote_backend_name/main.tf @@ -0,0 +1,13 @@ +terraform { + backend "remote" { + workspaces { + name = "github-actions" + } + } +} + +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/pull_request_remote_backend_prefix/.github/workflows/example.yml b/examples/pull_request_remote_backend_prefix/.github/workflows/example.yml new file mode 100644 index 00000000..654ae92c --- /dev/null +++ b/examples/pull_request_remote_backend_prefix/.github/workflows/example.yml @@ -0,0 +1,54 @@ +# An example workflow using the `remote` backend with `prefix`. +name: 'Terraform Workflow' +on: + - pull_request +jobs: + remote: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_comment: true + env: + # TF_CLI_ARGS_init is used to pass the backend configuration to the run. + # This way is chosen to replicate how one would use the `remote` backend with Terraform Cloud/Enterprise via automation. + TF_CLI_ARGS_init: '-backend-config="token=${{ secrets.TFP_TOKEN }}" -backend-config="organization=CHANGE_ME"' + # TF_WORKSPACE is needed to automatically select the workspace. + # This is concatenated to the end of the `prefix` defined within the `remote` backend. + # This workspace must already exist in Terraform Cloud/Enterprise. + TF_WORKSPACE: testing + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_comment: true + env: + # TF_WORKSPACE is needed to automatically select the workspace. + # This is concatenated to the end of the `prefix` defined within the `remote` backend. + # This workspace must already exist in Terraform Cloud/Enterprise. + TF_WORKSPACE: testing + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/pull_request_remote_backend_prefix/main.tf b/examples/pull_request_remote_backend_prefix/main.tf new file mode 100644 index 00000000..d94ebc94 --- /dev/null +++ b/examples/pull_request_remote_backend_prefix/main.tf @@ -0,0 +1,13 @@ +terraform { + backend "remote" { + workspaces { + prefix = "github-actions-" + } + } +} + +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/pull_request_working_dir/.github/workflows/example.yml b/examples/pull_request_working_dir/.github/workflows/example.yml new file mode 100644 index 00000000..8975616a --- /dev/null +++ b/examples/pull_request_working_dir/.github/workflows/example.yml @@ -0,0 +1,47 @@ +# An example workflow using local Terraform state with a working directory set. +name: 'Terraform Workflow' +on: + - pull_request +jobs: + root: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/pull_request_working_dir/nested/main.tf b/examples/pull_request_working_dir/nested/main.tf new file mode 100644 index 00000000..65507d56 --- /dev/null +++ b/examples/pull_request_working_dir/nested/main.tf @@ -0,0 +1,5 @@ +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/push_no_working_dir/.github/workflows/example.yml b/examples/push_no_working_dir/.github/workflows/example.yml new file mode 100644 index 00000000..de6ae40c --- /dev/null +++ b/examples/push_no_working_dir/.github/workflows/example.yml @@ -0,0 +1,53 @@ +# An example workflow using local Terraform state with no working directory set. +name: 'Terraform Workflow' +on: + push: + branches: + - master +jobs: + root: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Apply' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'apply' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/push_no_working_dir/main.tf b/examples/push_no_working_dir/main.tf new file mode 100644 index 00000000..65507d56 --- /dev/null +++ b/examples/push_no_working_dir/main.tf @@ -0,0 +1,5 @@ +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/push_remote_backend_name/.github/workflows/example.yml b/examples/push_remote_backend_name/.github/workflows/example.yml new file mode 100644 index 00000000..fe59becc --- /dev/null +++ b/examples/push_remote_backend_name/.github/workflows/example.yml @@ -0,0 +1,56 @@ +# An example workflow using the `remote` backend with `name`. +name: 'Terraform Workflow' +on: + push: + branches: + - master +jobs: + remote: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_comment: true + env: + # TF_CLI_ARGS_init is used to pass the backend configuration to the run. + # This way is chosen to replicate how one would use the `remote` backend with Terraform Cloud/Enterprise via automation. + TF_CLI_ARGS_init: '-backend-config="token=${{ secrets.TFP_TOKEN }}" -backend-config="organization=CHANGE_ME"' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Apply' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'apply' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/push_remote_backend_name/main.tf b/examples/push_remote_backend_name/main.tf new file mode 100644 index 00000000..367ccf6c --- /dev/null +++ b/examples/push_remote_backend_name/main.tf @@ -0,0 +1,13 @@ +terraform { + backend "remote" { + workspaces { + name = "github-actions" + } + } +} + +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/push_remote_backend_prefix/.github/workflows/example.yml b/examples/push_remote_backend_prefix/.github/workflows/example.yml new file mode 100644 index 00000000..0a527beb --- /dev/null +++ b/examples/push_remote_backend_prefix/.github/workflows/example.yml @@ -0,0 +1,68 @@ +# An example workflow using the `remote` backend with `prefix`. +name: 'Terraform Workflow' +on: + push: + branches: + - master +jobs: + remote: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_comment: true + env: + # TF_CLI_ARGS_init is used to pass the backend configuration to the run. + # This way is chosen to replicate how one would use the `remote` backend with Terraform Cloud/Enterprise via automation. + TF_CLI_ARGS_init: '-backend-config="token=${{ secrets.TFP_TOKEN }}" -backend-config="organization=CHANGE_ME"' + # TF_WORKSPACE is needed to automatically select the workspace. + # This is concatenated to the end of the `prefix` defined within the `remote` backend. + # This workspace must already exist in Terraform Cloud/Enterprise. + TF_WORKSPACE: testing + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_comment: true + env: + # TF_WORKSPACE is needed to automatically select the workspace. + # This is concatenated to the end of the `prefix` defined within the `remote` backend. + # This workspace must already exist in Terraform Cloud/Enterprise. + TF_WORKSPACE: testing + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Apply' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'apply' + tf_actions_comment: true + env: + # TF_WORKSPACE is needed to automatically select the workspace. + # This is concatenated to the end of the `prefix` defined within the `remote` backend. + # This workspace must already exist in Terraform Cloud/Enterprise. + TF_WORKSPACE: testing + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/push_remote_backend_prefix/main.tf b/examples/push_remote_backend_prefix/main.tf new file mode 100644 index 00000000..d94ebc94 --- /dev/null +++ b/examples/push_remote_backend_prefix/main.tf @@ -0,0 +1,13 @@ +terraform { + backend "remote" { + workspaces { + prefix = "github-actions-" + } + } +} + +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/examples/push_working_dir/.github/workflows/example.yml b/examples/push_working_dir/.github/workflows/example.yml new file mode 100644 index 00000000..ee704c49 --- /dev/null +++ b/examples/push_working_dir/.github/workflows/example.yml @@ -0,0 +1,58 @@ +# An example workflow using local Terraform state with a working directory set. +name: 'Terraform Workflow' +on: + push: + branches: + - master +jobs: + root: + name: 'Terraform Actions' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@master + - name: 'Terraform Format' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'fmt' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Init' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'init' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Validate' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'validate' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Plan' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'plan' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 'Terraform Apply' + uses: hashicorp/terraform-github-actions@master + with: + tf_actions_version: 0.12.13 + tf_actions_subcommand: 'apply' + tf_actions_working_dir: 'nested' + tf_actions_comment: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/examples/push_working_dir/nested/main.tf b/examples/push_working_dir/nested/main.tf new file mode 100644 index 00000000..65507d56 --- /dev/null +++ b/examples/push_working_dir/nested/main.tf @@ -0,0 +1,5 @@ +resource "null_resource" "root" { + triggers = { + value = "${timestamp()}" + } +} diff --git a/fmt/Dockerfile b/fmt/Dockerfile deleted file mode 100644 index 8357c168..00000000 --- a/fmt/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM hashicorp/terraform:0.12.12 - -LABEL "com.github.actions.name"="terraform fmt" -LABEL "com.github.actions.description"="Validate terraform files are formatted" -LABEL "com.github.actions.icon"="terminal" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="https://github.com/hashicorp/terraform-github-actions" -LABEL "homepage"="http://github.com/hashicorp/terraform-github-actions" -LABEL "maintainer"="HashiCorp Terraform Team " - -RUN apk --update --no-cache add jq curl bash - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/fmt/README.md b/fmt/README.md deleted file mode 100644 index df3ab3be..00000000 --- a/fmt/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Terraform Fmt Action -Runs `terraform fmt` to validate all Terraform files in a directory are in the canonical format. - If any files differ, this action will comment back on the pull request with the diffs of each file. - -See [https://www.terraform.io/docs/github-actions/actions/fmt.html](https://www.terraform.io/docs/github-actions/actions/fmt.html). diff --git a/fmt/entrypoint.sh b/fmt/entrypoint.sh deleted file mode 100755 index 91881e30..00000000 --- a/fmt/entrypoint.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh - -# stripcolors takes some output and removes ANSI color codes. -stripcolors() { - echo "$1" | sed 's/\x1b\[[0-9;]*m//g' -} - -set -e -cd "${TF_ACTION_WORKING_DIR:-.}" - -set +e -OUTPUT=$(sh -c "terraform fmt -check -list -recursive $*" 2>&1) -SUCCESS=$? -echo "$OUTPUT" -set -e - -if [ $SUCCESS -eq 0 ]; then - exit 0 -fi - -if [[ "$GITHUB_EVENT_NAME" == 'pull_request' ]]; then - if [ "$TF_ACTION_COMMENT" = "1" ] || [ "$TF_ACTION_COMMENT" = "false" ]; then - exit $SUCCESS - fi - - OUTPUT=$(stripcolors "$OUTPUT") - if [ $SUCCESS -eq 2 ]; then - # If it exits with 2, then there was a parse error and the command won't have - # printed out the files that have failed. In this case we comment back with the - # whole parse error. - COMMENT="\`\`\` -$OUTPUT -\`\`\` -" - else - # Otherwise the output will contain a list of unformatted filenames. - # Iterate through each file and build up a comment containing the diff - # of each file. - COMMENT="" - for file in $OUTPUT; do - FILE_DIFF=$(terraform fmt -no-color -write=false -diff "$file" | sed -n '/@@.*/,//{/@@.*/d;p}') - COMMENT="$COMMENT -
$file - -\`\`\`diff -$FILE_DIFF -\`\`\` -
-" - done - fi - - COMMENT_WRAPPER="#### \`terraform fmt\` Failed for \`$TF_ACTION_WORKING_DIRECTORY\` -$COMMENT -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`* -" - PAYLOAD=$(echo '{}' | jq --arg body "$COMMENT_WRAPPER" '.body = $body') - COMMENTS_URL=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.comments_url) - curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data "$PAYLOAD" "$COMMENTS_URL" > /dev/null -fi - -exit $SUCCESS diff --git a/init/Dockerfile b/init/Dockerfile deleted file mode 100644 index c813dd0f..00000000 --- a/init/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM hashicorp/terraform:0.12.12 - -LABEL "com.github.actions.name"="terraform init" -LABEL "com.github.actions.description"="Run terraform init" -LABEL "com.github.actions.icon"="download" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="https://github.com/hashicorp/terraform-github-actions" -LABEL "homepage"="http://github.com/hashicorp/terraform-github-actions" -LABEL "maintainer"="HashiCorp Terraform Team " - -RUN apk --update --no-cache add jq curl bash - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/init/README.md b/init/README.md deleted file mode 100644 index 7da40be0..00000000 --- a/init/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Terraform Init Action -Runs `terraform init` to initialize a Terraform working directory. This action will comment back on the pull request on failure. - -See [https://www.terraform.io/docs/github-actions/actions/init.html](https://www.terraform.io/docs/github-actions/actions/init.html). diff --git a/init/entrypoint.sh b/init/entrypoint.sh deleted file mode 100755 index 97fb4725..00000000 --- a/init/entrypoint.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh - -# stripcolors takes some output and removes ANSI color codes. -stripcolors() { - echo "$1" | sed 's/\x1b\[[0-9;]*m//g' -} - -set -e -cd "${TF_ACTION_WORKING_DIR:-.}" - -if [[ ! -z "$TF_ACTION_TFE_TOKEN" ]]; then - cat > ~/.terraformrc << EOF -credentials "${TF_ACTION_TFE_HOSTNAME:-app.terraform.io}" { - token = "$TF_ACTION_TFE_TOKEN" -} -EOF -fi - -set +e -export TF_APPEND_USER_AGENT="terraform-github-actions/1.0" -OUTPUT=$(sh -c "terraform init -input=false $*" 2>&1) -SUCCESS=$? -echo "$OUTPUT" -set -e - -if [ $SUCCESS -eq 0 ]; then - exit 0 -fi - -if [[ "$GITHUB_EVENT_NAME" == 'pull_request' ]]; then - if [ "$TF_ACTION_COMMENT" = "1" ] || [ "$TF_ACTION_COMMENT" = "false" ]; then - exit $SUCCESS - fi - - OUTPUT=$(stripcolors "$OUTPUT") - COMMENT="#### \`terraform init\` Failed for \`$TF_ACTION_WORKING_DIRECTORY\` -\`\`\` -$OUTPUT -\`\`\` -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`*" - PAYLOAD=$(echo '{}' | jq --arg body "$COMMENT" '.body = $body') - COMMENTS_URL=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.comments_url) - curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data "$PAYLOAD" "$COMMENTS_URL" > /dev/null -fi - -exit $SUCCESS - diff --git a/plan/Dockerfile b/plan/Dockerfile deleted file mode 100644 index 5fe32fdf..00000000 --- a/plan/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM hashicorp/terraform:0.12.12 - -LABEL "com.github.actions.name"="terraform plan" -LABEL "com.github.actions.description"="Run terraform plan" -LABEL "com.github.actions.icon"="book-open" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="https://github.com/hashicorp/terraform-github-actions" -LABEL "homepage"="http://github.com/hashicorp/terraform-github-actions" -LABEL "maintainer"="HashiCorp Terraform Team " - -RUN apk --update --no-cache add jq curl bash - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/plan/README.md b/plan/README.md deleted file mode 100644 index 78603db1..00000000 --- a/plan/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Terraform Plan Action -Runs `terraform plan` and comments back on the pull request with the plan output. - -See [https://www.terraform.io/docs/github-actions/actions/plan.html](https://www.terraform.io/docs/github-actions/actions/plan.html). diff --git a/plan/entrypoint.sh b/plan/entrypoint.sh deleted file mode 100755 index bbfb7c4a..00000000 --- a/plan/entrypoint.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/sh - -# stripcolors takes some output and removes ANSI color codes. -stripcolors() { - echo "$1" | sed 's/\x1b\[[0-9;]*m//g' -} - -# wrap takes some output and wraps it in a collapsible markdown section if -# it's over $TF_ACTION_WRAP_LINES long. -wrap() { - if [[ $(echo "$1" | wc -l) -gt ${TF_ACTION_WRAP_LINES:-20} ]]; then - echo " -
Show Output - -\`\`\`diff -$1 -\`\`\` - -
-" -else - echo " -\`\`\`diff -$1 -\`\`\` -" -fi -} - -set -e - -cd "${TF_ACTION_WORKING_DIR:-.}" - -if [[ ! -z "$TF_ACTION_TFE_TOKEN" ]]; then - cat > ~/.terraformrc << EOF -credentials "${TF_ACTION_TFE_HOSTNAME:-app.terraform.io}" { - token = "$TF_ACTION_TFE_TOKEN" -} -EOF -fi - -if [[ ! -z "$TF_ACTION_WORKSPACE" ]] && [[ "$TF_ACTION_WORKSPACE" != "default" ]]; then - terraform workspace select "$TF_ACTION_WORKSPACE" -fi - -set +e -OUTPUT=$(sh -c "TF_IN_AUTOMATION=true terraform plan -detailed-exitcode -input=false $*" 2>&1) -SUCCESS=$? -echo "$OUTPUT" -set -e - -# Detailed exit codes of the plan command include: -# - 0 = Succeeded with empty diff (no changes) -# - 1 = Error -# - 2 = Succeeded with non-empty diff (changes present) -CHANGES_PRESENT=false -if [ $SUCCESS -eq 2 ]; then - CHANGES_PRESENT=true - SUCCESS=0 -fi -echo ::set-output name=changes-present::$CHANGES_PRESENT - -if [ "$TF_ACTION_COMMENT" = "1" ] || [ "$TF_ACTION_COMMENT" = "false" ]; then - exit $SUCCESS -fi - -# Build the comment we'll post to the PR. -OUTPUT=$(stripcolors "$OUTPUT") -COMMENT="" -if [ $SUCCESS -ne 0 ]; then - OUTPUT=$(wrap "$OUTPUT") - COMMENT="#### \`terraform plan\` Failed for \`$TF_ACTION_WORKING_DIRECTORY\` -$OUTPUT - -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`*" -else - # Remove "Refreshing state..." lines by only keeping output after the - # delimiter (72 dashes) that represents the end of the refresh stage. - # We do this to keep the comment output smaller. - if echo "$OUTPUT" | egrep '^-{72}$'; then - OUTPUT=$(echo "$OUTPUT" | sed -n -r '/-{72}/,/-{72}/{ /-{72}/d; p }') - fi - - # Remove whitespace at the beginning of the line for added/modified/deleted - # resources so the diff markdown formatting highlights those lines. - OUTPUT=$(echo "$OUTPUT" | sed -r -e 's/^ \+/\+/g' | sed -r -e 's/^ ~/~/g' | sed -r -e 's/^ -/-/g') - - # Call wrap to optionally wrap our output in a collapsible markdown section. - OUTPUT=$(wrap "$OUTPUT") - - COMMENT="#### \`terraform plan\` Success for \`$TF_ACTION_WORKING_DIRECTORY\` -$OUTPUT - -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`*" -fi - -if [[ "$GITHUB_EVENT_NAME" == 'pull_request' ]]; then - # Post the comment. - PAYLOAD=$(echo '{}' | jq --arg body "$COMMENT" '.body = $body') - COMMENTS_URL=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.comments_url) - curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data "$PAYLOAD" "$COMMENTS_URL" > /dev/null -fi - -exit $SUCCESS diff --git a/src/main.sh b/src/main.sh new file mode 100755 index 00000000..554002ad --- /dev/null +++ b/src/main.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +function stripColors { + echo "${1}" | sed 's/\x1b\[[0-9;]*m//g' +} + +function parseInputs { + # Required inputs + if [ "${INPUT_TF_ACTIONS_VERSION}" != "" ]; then + tfVersion=${INPUT_TF_ACTIONS_VERSION} + else + echo "Input terraform_version cannot be empty" + exit 1 + fi + + if [ "${INPUT_TF_ACTIONS_SUBCOMMAND}" != "" ]; then + tfSubcommand=${INPUT_TF_ACTIONS_SUBCOMMAND} + else + echo "Input terraform_subcommand cannot be empty" + exit 1 + fi + + # Optional inputs + tfWorkingDir="." + if [ "${INPUT_TF_ACTIONS_WORKING_DIR}" != "" ] || [ "${INPUT_TF_ACTIONS_WORKING_DIR}" != "." ]; then + tfWorkingDir=${INPUT_TF_ACTIONS_WORKING_DIR} + fi + + tfComment=0 + if [ "${INPUT_TF_ACTIONS_COMMENT}" == "1" ] || [ "${INPUT_TF_ACTIONS_COMMENT}" == "true" ]; then + tfComment=1 + fi +} + +function installTerraform { + url="https://releases.hashicorp.com/terraform/${tfVersion}/terraform_${tfVersion}_linux_amd64.zip" + + echo "Downloading Terraform v${tfVersion}" + curl -s -S -L -o /tmp/terraform_${tfVersion} ${url} + if [ "${?}" -ne 0 ]; then + echo "Failed to download Terraform v${tfVersion}" + exit 1 + fi + echo "Successfully downloaded Terraform v${tfVersion}" + + echo "Unzipping Terraform v${tfVersion}" + unzip -d /usr/local/bin /tmp/terraform_${tfVersion} &> /dev/null + if [ "${?}" -ne 0 ]; then + echo "Failed to unzip Terraform v${tfVersion}" + exit 1 + fi + echo "Successfully unzipped Terraform v${tfVersion}" +} + +function main { + # Source the other files to gain access to their functions + scriptDir=$(dirname ${0}) + source ${scriptDir}/terraform_fmt.sh + source ${scriptDir}/terraform_init.sh + source ${scriptDir}/terraform_validate.sh + source ${scriptDir}/terraform_plan.sh + source ${scriptDir}/terraform_apply.sh + + parseInputs + cd ${GITHUB_WORKSPACE}/${tfWorkingDir} + + case "${tfSubcommand}" in + fmt) + installTerraform + terraformFmt + ;; + init) + installTerraform + terraformInit + ;; + validate) + installTerraform + terraformValidate + ;; + plan) + installTerraform + terraformPlan + ;; + apply) + installTerraform + terraformApply + ;; + *) + echo "Error: Must provide a valid value for terraform_subcommand" + exit 1 + ;; + esac +} + +main diff --git a/src/terraform_apply.sh b/src/terraform_apply.sh new file mode 100755 index 00000000..4f4b824b --- /dev/null +++ b/src/terraform_apply.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +function terraformApply { + # Gather the output of `terraform apply`. + echo "apply: info: applying Terraform configuration in ${tfWorkingDir}" + applyOutput=$(terraform apply -auto-approve -input=false 2>&1) + applyExitCode=${?} + + # Exit code of 0 indicates success. Print the output and exit. + if [ ${applyExitCode} -eq 0 ]; then + echo "apply: info: successfully applied Terraform configuration in ${tfWorkingDir}" + echo "${applyOutput}" + echo + exit ${applyExitCode} + fi + + # Exit code of !0 indicates failure. + echo "apply: error: failed to apply Terraform configuration in ${tfWorkingDir}" + echo "${applyOutput}" + echo + + # Comment on the pull request if necessary. + if [ "$GITHUB_EVENT_NAME" == "pull_request" ] && [ "${tfComment}" == "1" ]; then + applyCommentWrapper="#### \`terraform apply\` Failed +
Show Output + +\`\`\` +${applyOutput} +\`\`\` + +
+*Workflow: \`${GITHUB_WORKFLOW}\`, Action: \`${GITHUB_ACTION}\`, Working Directory: \`${tfWorkingDir}\`*" + + applyCommentWrapper=$(stripColors "${applyCommentWrapper}") + echo "apply: info: creating JSON" + applyPayload=$(echo '{}' | jq --arg body "${applyCommentWrapper}" '.body = $body') + applyCommentsURL=$(cat ${GITHUB_EVENT_PATH} | jq -r .pull_request.comments_url) + echo "apply: info: commenting on the pull request" + curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${applyPayload}" "${applyCommentsURL}" > /dev/null + fi + + exit ${applyExitCode} +} diff --git a/src/terraform_fmt.sh b/src/terraform_fmt.sh new file mode 100755 index 00000000..e2d533ef --- /dev/null +++ b/src/terraform_fmt.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +function terraformFmt { + # Gather the output of `terraform fmt`. + echo "fmt: info: checking if Terraform files in ${tfWorkingDir} are correctly formatted" + fmtOutput=$(terraform fmt -check -write=false -diff -recursive 2>&1) + fmtExitCode=${?} + + # Exit code of 0 indicates success. Print the output and exit. + if [ ${fmtExitCode} -eq 0 ]; then + echo "fmt: info: Terraform files in ${tfWorkingDir} are correctly formatted" + echo "${fmtOutput}" + echo + exit ${fmtExitCode} + fi + + # Exit code of 2 indicates a parse error. Print the output and exit. + if [ ${fmtExitCode} -eq 2 ]; then + echo "fmt: error: failed to parse Terraform files" + echo "${fmtOutput}" + echo + exit ${fmtExitCode} + fi + + # Exit code of !0 and !2 indicates failure. + echo "fmt: error: Terraform files in ${tfWorkingDir} are incorrectly formatted" + echo "${fmtOutput}" + echo + echo "fmt: error: the following files in ${tfWorkingDir} are incorrectly formatted" + fmtFileList=$(terraform fmt -check -write=false -list -recursive 2>&1) + echo "${fmtFileList}" + echo + + # Comment on the pull request if necessary. + if [ "$GITHUB_EVENT_NAME" == "pull_request" ] && [ "${tfComment}" == "1" ]; then + fmtComment="" + for file in ${fmtFileList}; do + fmtFileDiff=$(terraform fmt -check -write=false -diff "$file" | sed -n '/@@.*/,//{/@@.*/d;p}') + fmtComment="${fmtComment} +
${tfWorkingDir}/${file} + +\`\`\`diff +${fmtFileDiff} +\`\`\` + +
" + + done + + fmtCommentWrapper="#### \`terraform fmt\` Failed +${fmtComment} +*Workflow: \`${GITHUB_WORKFLOW}\`, Action: \`${GITHUB_ACTION}\`, Working Directory: \`${tfWorkingDir}\`*" + + fmtCommentWrapper=$(stripColors "${fmtCommentWrapper}") + echo "fmt: info: creating JSON" + fmtPayload=$(echo '{}' | jq --arg body "${fmtCommentWrapper}" '.body = $body') + fmtCommentsURL=$(cat ${GITHUB_EVENT_PATH} | jq -r .pull_request.comments_url) + echo "fmt: info: commenting on the pull request" + curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${fmtPayload}" "${fmtCommentsURL}" > /dev/null + fi + + exit ${fmtExitCode} +} diff --git a/src/terraform_init.sh b/src/terraform_init.sh new file mode 100755 index 00000000..52d82715 --- /dev/null +++ b/src/terraform_init.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +function terraformInit { + # Gather the output of `terraform init`. + echo "init: info: initializing Terraform configuration in ${tfWorkingDir}" + initOutput=$(terraform init -input=false 2>&1) + initExitCode=${?} + + # Exit code of 0 indicates success. Print the output and exit. + if [ ${initExitCode} -eq 0 ]; then + echo "init: info: successfully initialized Terraform configuration in ${tfWorkingDir}" + echo "${initOutput}" + echo + exit ${initExitCode} + fi + + # Exit code of !0 indicates failure. + echo "init: error: failed to initialize Terraform configuration in ${tfWorkingDir}" + echo "${initOutput}" + echo + + # Comment on the pull request if necessary. + if [ "$GITHUB_EVENT_NAME" == "pull_request" ] && [ "${tfComment}" == "1" ]; then + initCommentWrapper="#### \`terraform init\` Failed + +\`\`\` +${initOutput} +\`\`\` + +*Workflow: \`${GITHUB_WORKFLOW}\`, Action: \`${GITHUB_ACTION}\`, Working Directory: \`${tfWorkingDir}\`*" + + initCommentWrapper=$(stripColors "${initCommentWrapper}") + echo "init: info: creating JSON" + initPayload=$(echo '{}' | jq --arg body "${initCommentWrapper}" '.body = $body') + initCommentsURL=$(cat ${GITHUB_EVENT_PATH} | jq -r .pull_request.comments_url) + echo "init: info: commenting on the pull request" + curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${initPayload}" "${initCommentsURL}" > /dev/null + fi + + exit ${initExitCode} +} diff --git a/src/terraform_plan.sh b/src/terraform_plan.sh new file mode 100755 index 00000000..3c78d301 --- /dev/null +++ b/src/terraform_plan.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +function terraformPlan { + # Gather the output of `terraform plan`. + echo "plan: info: planning Terraform configuration in ${tfWorkingDir}" + planOutput=$(terraform plan -detailed-exitcode -input=false 2>&1) + planExitCode=${?} + planHasChanges=false + planCommentStatus="Failed" + + # Exit code of 0 indicates success with no changes. Print the output and exit. + if [ ${planExitCode} -eq 0 ]; then + echo "plan: info: successfully planned Terraform configuration in ${tfWorkingDir}" + echo "${planOutput}" + echo + exit ${planExitCode} + fi + + # Exit code of 2 indicates success with changes. Print the output, change the + # exit code to 0, and mark that the plan has changes. + if [ ${planExitCode} -eq 2 ]; then + planExitCode=0 + planHasChanges=true + planCommentStatus="Success" + echo "plan: info: successfully planned Terraform configuration in ${tfWorkingDir}" + echo "${planOutput}" + echo + if echo "${planOutput}" | egrep '^-{72}$' &> /dev/null; then + planOutput=$(echo "${planOutput}" | sed -n -r '/-{72}/,/-{72}/{ /-{72}/d; p }') + fi + planOutput=$(echo "${planOutput}" | sed -r -e 's/^ \+/\+/g' | sed -r -e 's/^ ~/~/g' | sed -r -e 's/^ -/-/g') + fi + + # Exit code of !0 indicates failure. + if [ ${planExitCode} -ne 0 ]; then + echo "plan: error: failed to plan Terraform configuration in ${tfWorkingDir}" + echo "${planOutput}" + echo + fi + + # Comment on the pull request if necessary. + if [ "$GITHUB_EVENT_NAME" == "pull_request" ] && [ "${tfComment}" == "1" ] && ([ "${planHasChanges}" == "true" ] || [ "${planCommentStatus}" == "Failed" ]); then + planCommentWrapper="#### \`terraform plan\` ${planCommentStatus} +
Show Output + +\`\`\` +${planOutput} +\`\`\` + +
+*Workflow: \`${GITHUB_WORKFLOW}\`, Action: \`${GITHUB_ACTION}\`, Working Directory: \`${tfWorkingDir}\`*" + + planCommentWrapper=$(stripColors "${planCommentWrapper}") + echo "plan: info: creating JSON" + planPayload=$(echo '{}' | jq --arg body "${planCommentWrapper}" '.body = $body') + planCommentsURL=$(cat ${GITHUB_EVENT_PATH} | jq -r .pull_request.comments_url) + echo "plan: info: commenting on the pull request" + curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${planPayload}" "${planCommentsURL}" > /dev/null + fi + + echo ::set-output name=tf_actions_plan_has_changes::${planHasChanges} + exit ${planExitCode} +} diff --git a/src/terraform_validate.sh b/src/terraform_validate.sh new file mode 100755 index 00000000..9854d12e --- /dev/null +++ b/src/terraform_validate.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +function terraformValidate { + # Gather the ouput of `terraform validate`. + echo "validate: info: validating Terraform configuration in ${tfWorkingDir}" + validateOutput=$(terraform validate 2>&1) + validateExitCode=${?} + + # Exit code of 0 indicates success. Print the output and exit. + if [ ${validateExitCode} -eq 0 ]; then + echo "validate: info: successfully validated Terraform configuration in ${tfWorkingDir}" + echo "${validateOutput}" + echo + exit ${validateExitCode} + fi + + # Exit code of !0 indicates failure. + echo "validate: error: failed to validate Terraform configuration in ${tfWorkingDir}" + echo "${validateOutput}" + echo + + # Comment on the pull request if necessary. + if [ "$GITHUB_EVENT_NAME" == "pull_request" ] && [ "${tfComment}" == "1" ]; then + validateCommentWrapper="#### \`terraform validate\` Failed + +\`\`\` +${validateOutput} +\`\`\` + +*Workflow: \`${GITHUB_WORKFLOW}\`, Action: \`${GITHUB_ACTION}\`, Working Directory: \`${tfWorkingDir}\`*" + + validateCommentWrapper=$(stripColors "${validateCommentWrapper}") + echo "validate: info: creating JSON" + validatePayload=$(echo '{}' | jq --arg body "${validateCommentWrapper}" '.body = $body') + validateCommentsURL=$(cat ${GITHUB_EVENT_PATH} | jq -r .pull_request.comments_url) + echo "validate: info: commenting on the pull request" + curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${validatePayload}" "${validateCommentsURL}" > /dev/null + fi + + exit ${validateExitCode} +} diff --git a/validate/Dockerfile b/validate/Dockerfile deleted file mode 100644 index 63242ca7..00000000 --- a/validate/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM hashicorp/terraform:0.12.12 - -LABEL "com.github.actions.name"="terraform validate" -LABEL "com.github.actions.description"="Validate the terraform files in a directory" -LABEL "com.github.actions.icon"="alert-triangle" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="https://github.com/hashicorp/terraform-github-actions" -LABEL "homepage"="http://github.com/hashicorp/terraform-github-actions" -LABEL "maintainer"="HashiCorp Terraform Team " - -RUN apk --update --no-cache add jq curl bash - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/validate/README.md b/validate/README.md deleted file mode 100644 index d8546533..00000000 --- a/validate/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Terraform Validate Action -Runs `terraform validate` to validate the terraform files in a directory. - -See [https://www.terraform.io/docs/github-actions/actions/validate.html](https://www.terraform.io/docs/github-actions/actions/validate.html). diff --git a/validate/entrypoint.sh b/validate/entrypoint.sh deleted file mode 100755 index edecd73f..00000000 --- a/validate/entrypoint.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -# stripcolors takes some output and removes ANSI color codes. -stripcolors() { - echo "$1" | sed 's/\x1b\[[0-9;]*m//g' -} - -set -e -cd "${TF_ACTION_WORKING_DIR:-.}" - -if [[ ! -z "$TF_ACTION_WORKSPACE" ]] && [[ "$TF_ACTION_WORKSPACE" != "default" ]]; then - terraform workspace select "$TF_ACTION_WORKSPACE" -fi - -set +e -OUTPUT=$(sh -c "terraform validate $*" 2>&1) -SUCCESS=$? -echo "$OUTPUT" -set -e - -if [ $SUCCESS -eq 0 ]; then - exit 0 -fi - -if [[ "$GITHUB_EVENT_NAME" == 'pull_request' ]]; then - if [ "$TF_ACTION_COMMENT" = "1" ] || [ "$TF_ACTION_COMMENT" = "false" ]; then - exit $SUCCESS - fi - - OUTPUT=$(stripcolors "$OUTPUT") - COMMENT="#### \`terraform validate\` Failed for \`$TF_ACTION_WORKING_DIRECTORY\` -\`\`\` -$OUTPUT -\`\`\` -*Workflow: \`$GITHUB_WORKFLOW\`, Action: \`$GITHUB_ACTION\`*" - PAYLOAD=$(echo '{}' | jq --arg body "$COMMENT" '.body = $body') - COMMENTS_URL=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.comments_url) - curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data "$PAYLOAD" "$COMMENTS_URL" > /dev/null -fi - -exit $SUCCESS