diff --git a/.github/workflows/apply.yaml b/.github/workflows/apply.yaml index 3f883a6..c1da8c0 100644 --- a/.github/workflows/apply.yaml +++ b/.github/workflows/apply.yaml @@ -30,19 +30,23 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + CONFIG_APP_CLIENT_ID: Iv23lipEOAvwk5QqNUie TF_CLI_ARGS: "-no-color" TF_IN_AUTOMATION: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 with: - client-id: Iv23lipEOAvwk5QqNUie + client-id: ${{ env.CONFIG_APP_CLIENT_ID }} private-key: ${{ secrets.CONFIG_APP_SECRET }} owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} - name: Setup OpenTofu - uses: opentofu/setup-opentofu@v2 + uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2 with: tofu_wrapper: false - name: TF init diff --git a/.github/workflows/plan.yaml b/.github/workflows/plan.yaml new file mode 100644 index 0000000..6d9f970 --- /dev/null +++ b/.github/workflows/plan.yaml @@ -0,0 +1,128 @@ +name: Plan + +on: + pull_request_target: + paths: + - "**/*.tf" + - "**/*.csv" + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + plan: + runs-on: ubuntu-latest + environment: plan + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + CONFIG_APP_CLIENT_ID: Iv23lipEOAvwk5QqNUie + TF_CLI_ARGS: "-no-color" + TF_IN_AUTOMATION: "true" + steps: + - name: Checkout PR + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + with: + client-id: ${{ env.CONFIG_APP_CLIENT_ID }} + private-key: ${{ secrets.CONFIG_APP_SECRET }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + permission-contents: read + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2 + with: + tofu_wrapper: false + + - name: TF init + run: tofu init + + - name: TF Validate + run: tofu validate + + - name: TF Lint + uses: terraform-linters/setup-tflint@6e1e0642c0289bd619021bf6b34e3c08ed1e005a # v6.3.0 + - run: tflint --init && tflint + + - name: TF Plan + id: plan + run: | + set +e + tofu plan -detailed-exitcode 2>&1 | tee plan_output.txt + exitcode=${PIPESTATUS[0]} + echo "exitcode=$exitcode" >> "$GITHUB_OUTPUT" + if [ "$exitcode" -eq 1 ]; then + exit 1 + fi + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + + - name: Comment plan on PR + if: always() && steps.plan.outcome != 'skipped' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + const fs = require('fs'); + const raw = fs.readFileSync('plan_output.txt', 'utf8'); + const plan = raw.replace(/&/g, '&').replace(//g, '>'); + const exitcode = '${{ steps.plan.outputs.exitcode }}'; + + let summary; + if (exitcode === '0') { + summary = 'No changes. Infrastructure is up-to-date.'; + } else if (exitcode === '2') { + summary = 'Changes detected. Review the plan below.'; + } else { + summary = 'Plan failed. See details below.'; + } + + const marker = ''; + const body = [ + marker, + `### OpenTofu Plan`, + '', + summary, + '', + '
Show plan output', + '', + `
${plan.substring(0, 60000)}
`, + '', + '
', + '', + `*Commit: ${context.payload.pull_request.head.sha}*`, + ].join('\n'); + + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => c.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 1a2df67..307ce4c 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -13,13 +13,15 @@ jobs: env: TF_CLI_ARGS: "-no-color" steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: "3.13" - - uses: opentofu/setup-opentofu@v2 + - uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2 with: tofu_wrapper: false - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 with: extra_args: --all-files diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 0000000..9276449 --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,36 @@ +name: Validate + +on: + pull_request: + paths: + - "**/*.tf" + - "**/*.csv" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + env: + TF_CLI_ARGS: "-no-color" + TF_IN_AUTOMATION: "true" + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2 + with: + tofu_wrapper: false + + - name: TF init (no backend) + run: tofu init -backend=false + + - name: TF Validate + run: tofu validate