TerraformCloud + GithubActionsを使ってPRのコメントにplan結果を出してみる

TerraformCloud

個人で検証がてらtryしてみた話

  • TerraformCloudは簡単にいうと以下です
    • チームがTerraformを使用してインフラストラクチャのリソースを準備するのに役立つアプリケーション
    • できること
      • 一貫した信頼性の高い環境でのTerraformの実行を管理
      • 機密データへの簡単なアクセス(共同利用者間で共有されている)
      • 変更を承認するためのアクセス制御
      • モジュールを共有するためのプライベートレジストリ
      • 構成内容を管理するための詳細なポリシー制御
    • と書かれています。
    • 個人で遊んだりする分には、Freeプランで十分だと思います。今回はFreeプランで作業をします

今回やってみたこと

  • Github Actionsを使用してPlan結果をPRのコメントに表示させるということをします。
    • またコメントは必ず最新のcommitに対してのplan結果のみを表示するようにします。

完成形

準備すること

  • TerraformCloudのアカウント
  • AWSのアカウント(今回はAWSのリソースをTerraformで作成するという前提です)
    • 若干のAWSの知識が必要
    • コピペでもおkです

Terraform Cloud

  • Workspaceというものを作成します。
    • こちらの作成方法ですがTerraformを使用して作成します。
    • 他にやり方を見つけられなかったので知っている方いたら教えて欲しいです
terraform {
  cloud {
    organization = "hironeko"
    workspaces {
      name = "tf-cloud-sample"
    }
  }
}

こちらをmain.tf などに書いてもらい、 以下のコマンドを実行します

terraform login

ブラウザを開くのでAPIのtokenを作成し作成されたらコピーしておきます。

再び、ターミナルを開きコピーしたAPI tokenを貼ります。ターミナル上は表示されませんがエンターを押してもらい完了です。

最後に下記コマンドを実行してください

terraform init

これで下準備は完了です。

実装

  • main.tfを下記のようにひとまず書いておきます
terraform {
  cloud {
    organization = "hironeko"
    workspaces {
      name = "tf-cloud-sample"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

variable "sample_instance_type" {
  default = "t2.micro"
}

resource "aws_instance" "sample" {
  ami           = "ami-05ffd9ad4ddd0d6e2"
  instance_type = var.sample_instance_type

  tags = {
    Name = "sample"
  }
}

Terraform Cloudでplanを実行可能にする

  • Terraform Cloudでplanを可能にするには、IAMユーザーのクレデンシャル情報が必要になります。なので発行しましょう
    • TerraformCloudでWorkspaces > Variablesを開きます
    • Add variableボタンを押下し、Environment variableを選択してkey / valueを入れます。
    • KEY
    • これでAWSリソースを作成するためのplanを実行することが可能になります。

今回はAPI経由でplanを行うのでこの設定を行なっています

Github Actionsを書く

name: "Terraform"

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  terraform:
    name: "Terraform"
    runs-on: ubuntu-latest

    env:
      tf_version: "1.5.3"

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.tf_version }}
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform Format
        id: fmt
        run: terraform fmt -check -recursive

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform Plan
        id: plan
        if: github.event_name == 'pull_request'
        run: terraform plan -no-color -input=false
        continue-on-error: true

      - name: Remove old comment
        id: remove_old_comment
        uses: actions/github-script@0.9.0
        if: github.event_name == 'pull_request'
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            opts = github.issues.listComments.endpoint.merge({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              per_page: 100,
            })
            const comments = await github.paginate(opts)
            for(const comment of comments) {
              if (comment.user.login === "github-actions[bot]") {
                github.issues.deleteComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  comment_id: comment.id,
                })
              }
            }

      - name: Update Pull Request
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`

            <details><summary>Show Plan</summary>

            \`\`\`\n
            ${process.env.PLAN}
            \`\`\`

            </details>

            *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      - name: Terraform Plan Status
        if: steps.plan.outcome == 'failure'
        run: exit 1

PushしてPR作成すれば終わりです

参考にした記事

dev.classmethod.jp

www.ai-shift.co.jp

studist.tech

とても助かりました。ありがとうございます。

また記載しているコードのほとんどを上記記事の内容を流用しています。