실행
더 많은 작업
워크플로는 잡마다 액션을 한 번씩 부르고, step에는 그 잡의 이름을 넣습니다. 적용 잡의 환경 이름은 워크스페이스 이름과 같습니다.
기본값은 HCP Terraform과 같은 순서로, 머지한 뒤에 적용합니다. 풀 리퀘스트에서는 플랜만 하고, 머지되면 기본 브랜치로의 푸시가 다시 플랜한 다음 환경이 승인하면 적용합니다. auto-apply에 있는 워크스페이스는 바로 적용합니다. pending, apply, merge 스텝에 apply-before-merge를 "true"로 주면 풀 리퀘스트를 먼저 적용하고, 적용이 성공하면 머지합니다.
스텝
step |
잡 | 하는 일 |
|---|---|---|
plan |
plan, 플랜 전 | 풀 리퀘스트가 워크스페이스 디렉터리, 이 액션을 부른 워크플로, shared-paths 중 어느 것도 바꾸지 않으면 plan 출력을 "false"로 냅니다. 뒤 스텝은 if: steps.<id>.outputs.plan == 'true'로 건너뛰면 됩니다. require-up-to-date를 켜면 베이스보다 뒤처진 풀 리퀘스트를 거부합니다. dflook이 내려받는 프로바이더를 워크스페이스의 .terraform.lock.hcl을 키로 캐시에서 복원하고, 잡이 끝날 때 저장합니다.
|
changes |
plan, 플랜 후 | 워크스페이스에 적용할 변경이 있다고 표시합니다. |
pending |
플랜이 끝난 뒤의 잡 하나 | 적용할 워크스페이스를 고릅니다. 환경이 없거나, auto-apply에 없는 워크스페이스인데 필수 리뷰어가 없으면 거부합니다. 같은 브랜치에서 아직 승인을 기다리는 이전 실행은 취소하는데, 잡이 하나라도 돌고 있으면 두지 않습니다. 풀 리퀘스트에서는 아무것도 고르지 않습니다. apply-before-merge일 때만 고르고, 이때 스택 위쪽의 풀 리퀘스트가 워크스페이스 둘 이상을 바꾸면 실패합니다.
|
apply |
apply, 첫 스텝 | plan 잡이 캐시한 프로바이더를 복원해서, 적용에서 다시 내려받지 않습니다. 누가 승인했는지, 또는 승인이 필요 없었는지 기록합니다. 푸시에서는 커밋이 여전히 브랜치의 헤드일 때만 진행합니다. 풀 리퀘스트에서는 apply-before-merge가 아니면 거부하고, 맞더라도 풀 리퀘스트가 움직였거나 머지됐을 때, 네이티브 스택 밖에서 다른 브랜치를 향할 때, require-up-to-date인데 뒤처졌을 때 거부합니다.
|
result |
apply, 마지막 스텝, if: always() |
적용으로 무엇이 바뀌었고 성공했는지 남깁니다. |
gate |
필수 검사 | 의존하는 잡이 모두 성공해야 통과합니다. 적용 잡은 건너뛰어도 됩니다. |
merge |
게이트 뒤, apply-before-merge일 때 |
비동기 머지 API로 풀 리퀘스트를 머지합니다. 스택이면 그 아래의 풀 리퀘스트도 함께 머지됩니다. 이번 실행에 적용할 것이 없으면, 앞선 실행이 승인을 받았거나 자동 적용 워크스페이스를 적용했을 때만 머지합니다. 적용 뒤 2분이 지나도 머지되지 않으면 실패합니다. 프로덕션이 기본 브랜치보다 앞서 있게 되기 때문입니다. apply-before-merge가 아니면 아무것도 하지 않습니다.
|
collapse |
plan, 마지막 스텝, if: always() |
dflook/tofu-plan이나 dflook/terraform-plan의 플랜 댓글을 워크스페이스마다 최신 하나만 남기고 오래된 것으로 숨깁니다. **<workspace> apply: 로 시작하는 댓글도 같은 방식으로 숨깁니다.
|
플랜 댓글
plan 잡의 dflook/tofu-plan이 워크스페이스마다 플랜을 풀 리퀘스트에 댓글로 남깁니다. 다시 푸시하면 다시 플랜하고, collapse 스텝이 이전 플랜 댓글을 오래된 것으로 숨겨서 워크스페이스마다 최신 댓글만 펼쳐져 있습니다.
적용 잡은 플랜을 새로 만들어 승인받은 것과 같을 때만 적용해야 합니다. plan_path나 auto_approve 없이 쓴 dflook/tofu-apply가 그렇게 동작합니다. 푸시에서는 dflook의 플랜이 머지된 풀 리퀘스트의 플랜 댓글을 기본 브랜치의 플랜으로 고쳐 쓰고, 적용은 그 댓글과 비교합니다.
어느 커밋이 적용되나
기본 브랜치의 최신 커밋만 적용됩니다. 그 플랜에 아직 적용되지 않은 앞선 커밋의 변경이 모두 들어 있기 때문입니다. 그래서 푸시 실행은 변경이 있는 워크스페이스를 모두 플랜해야 하고, plan 스텝의 plan 출력은 풀 리퀘스트가 아니면 언제나 "true"입니다. workflow_dispatch 적용처럼 플랜 뒤에 생긴 변경이 있으면 새 플랜이 달라져서 적용이 멈춥니다. 이때는 "Re-run all jobs"로 다시 플랜하면 됩니다. 풀 리퀘스트 없이 기본 브랜치에 푸시한 커밋은 플랜 댓글이 없으니 workflow_dispatch로 적용하세요.
잡 요약
스텝마다 실행 페이지에 요약을 남겨서, 리뷰어가 로그를 열지 않고도 무엇을 승인하는지 볼 수 있습니다.
step |
요약 |
|---|---|
changes |
플랜이 추가, 변경, 삭제, 이동, 가져오기하는 리소스 수, 접어 둔 플랜 본문, 승인을 기다리는지 여부. |
pending |
적용할 환경과 각 환경을 승인할 수 있는 사람. |
apply |
승인한 사람(승인이 필요 없었다면 실행을 시작한 사람)과 적용하는 커밋. |
result |
적용 성공 여부와, changes와 같은 수와 플랜.
|
gate |
의존하는 잡과 각 잡의 결과. |
merge |
풀 리퀘스트가 머지됐는지, 안 됐다면 그 이유. |
전체 워크플로
시작하기의 워크플로에 머지 뒤에 도는 스텝을 모두 넣은 것입니다. workflow_dispatch 실행은 고른 워크스페이스를 그 브랜치에서 환경이 승인하면 적용하고, collapse 스텝은 지난 플랜 댓글을 숨기고, 잡 요약에는 플랜이 접혀서 들어갑니다.
name: tofu
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
inputs:
workspace:
description: The workspace to apply from this branch, once its environment approves.
required: true
type: choice
options: &workspaces [network, dns]
permissions: {}
jobs:
plan:
name: ${{ matrix.workspace }} plan
if: github.event_name != 'workflow_dispatch'
strategy:
fail-fast: false
matrix:
workspace: *workspaces
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: femiwiki/terraform-github-tacos@v0.7.0
with:
step: plan
workspace: ${{ matrix.workspace }}
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dflook/tofu-plan@c7416238983bdd94c5486fb856bb7358f2bcc935 # v3.1.0
id: plan
env:
GITHUB_TOKEN: ${{ github.token }}
with:
path: ${{ matrix.workspace }}
- uses: femiwiki/terraform-github-tacos@v0.7.0
with:
step: changes
workspace: ${{ matrix.workspace }}
changes: ${{ steps.plan.outputs.changes }}
plan-json: ${{ steps.plan.outputs.json_plan_path }}
plan-text: ${{ steps.plan.outputs.text_plan_path }}
- uses: femiwiki/terraform-github-tacos@v0.7.0
if: always()
with:
step: collapse
pending:
needs: plan
if: >-
!cancelled() && (github.event_name == 'workflow_dispatch'
|| (github.event_name == 'push' && needs.plan.result == 'success'))
runs-on: ubuntu-latest
permissions:
actions: write
checks: read
outputs:
workspaces: ${{ steps.pending.outputs.workspaces }}
steps:
- uses: femiwiki/terraform-github-tacos@v0.7.0
id: pending
with:
step: pending
workspaces: ${{ inputs.workspace && format('["{0}"]', inputs.workspace) || '' }}
apply:
name: ${{ matrix.workspace }} apply
needs: pending
if: >-
!cancelled() && needs.pending.result == 'success'
&& needs.pending.outputs.workspaces != '[]'
strategy:
fail-fast: false
matrix:
workspace: ${{ fromJSON(needs.pending.outputs.workspaces) }}
runs-on: ubuntu-latest
environment: ${{ matrix.workspace }}
permissions:
actions: read
contents: read
pull-requests: write
steps:
- uses: femiwiki/terraform-github-tacos@v0.7.0
id: approver
with:
step: apply
workspace: ${{ matrix.workspace }}
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dflook/tofu-apply@e3e5dfc8f935d4b152c767801046f63abf8a77d3 # v3.1.0
id: apply
env:
GITHUB_TOKEN: ${{ github.token }}
with:
path: ${{ matrix.workspace }}
auto_approve: ${{ github.event_name == 'workflow_dispatch' }}
- uses: femiwiki/terraform-github-tacos@v0.7.0
if: always()
with:
step: result
workspace: ${{ matrix.workspace }}
outcome: ${{ steps.apply.outcome }}
plan-json: ${{ steps.apply.outputs.json_plan_path }}
plan-text: ${{ steps.apply.outputs.text_plan_path }}
approver: ${{ steps.approver.outputs.approver }}
# Make this the one required check
gate:
name: ${{ github.event_name == 'pull_request' && 'tofu gate' || 'tofu gate (pull requests only)' }}
needs: plan
if: always() && github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions: {}
steps:
- uses: femiwiki/terraform-github-tacos@v0.7.0
with:
step: gate
needs: ${{ toJSON(needs) }}