Skip to content

Commit 2a08e55

Browse files
committed
feat: 서버 배포 스크립트 추가 및 Slack 배포 알림을 위한 GitHub Action 추가
1 parent eeae357 commit 2a08e55

3 files changed

Lines changed: 197 additions & 63 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Notify Slack of deploy result
2+
description: Send a Slack chat.postMessage reflecting the calling job's status. The status emoji ('성공 :tada:', '취소 :no_entry:', '실패 :rotating_light:') is auto-appended to the header — the caller supplies the prefix.
3+
4+
inputs:
5+
slack-token:
6+
description: Slack bot token (xoxb-...)
7+
required: true
8+
slack-channel:
9+
description: Slack channel ID
10+
required: true
11+
header-prefix:
12+
description: Header text shown before the auto-appended status emoji (e.g. "frontend → infrastructure (dev)")
13+
required: true
14+
section-text:
15+
description: Section body in mrkdwn. Caller can reference job.status / step outputs to vary by outcome.
16+
required: false
17+
default: GitHub Action 바로가기
18+
19+
runs:
20+
using: composite
21+
steps:
22+
- name: Notify Slack
23+
uses: slackapi/slack-github-action@v3
24+
with:
25+
method: chat.postMessage
26+
token: ${{ inputs.slack-token }}
27+
payload: |
28+
channel: "${{ inputs.slack-channel }}"
29+
blocks:
30+
- type: header
31+
text:
32+
type: plain_text
33+
text: "${{ inputs.header-prefix }} ${{ job.status == 'success' && '성공 :tada:' || job.status == 'cancelled' && '취소 :no_entry:' || '실패 :rotating_light:' }}"
34+
emoji: true
35+
- type: section
36+
text:
37+
type: mrkdwn
38+
text: "${{ inputs.section-text }}"
39+
accessory:
40+
type: button
41+
text:
42+
type: plain_text
43+
text: "${{ github.run_id }}"
44+
value: github_action
45+
url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
46+
action_id: button-action

.github/workflows/deploy-infra.yml

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
name: Deploy frontend to infrastructure
2+
3+
concurrency:
4+
group: deploy-infra-${{ github.ref }}-${{ inputs.WORKFLOW_PHASE || 'dev' }}
5+
cancel-in-progress: true
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
WORKFLOW_PHASE:
11+
description: "Phase to deploy"
12+
required: true
13+
default: dev
14+
type: choice
15+
options:
16+
- dev
17+
- prd
18+
push:
19+
branches:
20+
- main
21+
22+
permissions:
23+
contents: read
24+
25+
jobs:
26+
config:
27+
runs-on: ubuntu-latest
28+
outputs:
29+
phase: ${{ steps.set.outputs.phase }}
30+
matrix: ${{ steps.set.outputs.matrix }}
31+
keys: ${{ steps.set.outputs.keys }}
32+
env:
33+
PHASE: ${{ github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev' }}
34+
steps:
35+
- id: set
36+
run: |
37+
set -euo pipefail
38+
# Per-phase {key, app} list. `key` matches infra repo's vars.yaml
39+
# `frontends.<key>` and is also the upload-artifact name suffix.
40+
# `mode` is derived from PHASE at build time, not encoded here.
41+
case "$PHASE" in
42+
dev)
43+
INCLUDE='[
44+
{"key":"admin-dev","app":"pyconkr-admin"},
45+
{"key":"participant-dev","app":"pyconkr-participant-portal"},
46+
{"key":"pyconkr-dev","app":"pyconkr-2026"}
47+
]' ;;
48+
prd)
49+
INCLUDE='[
50+
{"key":"admin","app":"pyconkr-admin"},
51+
{"key":"participant","app":"pyconkr-participant-portal"},
52+
{"key":"pyconkr-2026","app":"pyconkr-2026"},
53+
{"key":"pyconkr-2025","app":"pyconkr-2025"}
54+
]' ;;
55+
esac
56+
MATRIX=$(jq -nc --argjson inc "$INCLUDE" '{include: $inc}')
57+
KEYS=$(echo "$MATRIX" | jq -c '[.include[].key]')
58+
{
59+
echo "phase=$PHASE"
60+
echo "matrix=$MATRIX"
61+
echo "keys=$KEYS"
62+
} >> "$GITHUB_OUTPUT"
63+
64+
build:
65+
needs: config
66+
runs-on: ubuntu-latest
67+
strategy:
68+
fail-fast: false
69+
matrix: ${{ fromJson(needs.config.outputs.matrix) }}
70+
env:
71+
VITE_MODE: ${{ needs.config.outputs.phase == 'prd' && 'production' || 'development' }}
72+
steps:
73+
- uses: actions/checkout@v6
74+
75+
- uses: pnpm/action-setup@v6
76+
77+
- uses: actions/setup-node@v6
78+
with:
79+
node-version: "24"
80+
cache: "pnpm"
81+
cache-dependency-path: "pnpm-lock.yaml"
82+
83+
- run: pnpm install --frozen-lockfile
84+
85+
- name: Build ${{ matrix.app }} (${{ env.VITE_MODE }})
86+
run: pnpm build:@apps/${{ matrix.app }} --mode ${{ env.VITE_MODE }}
87+
88+
# Artifact name = `frontend-<key>` (infra repo vars.yaml `frontends.<key>`).
89+
- uses: actions/upload-artifact@v7
90+
with:
91+
name: frontend-${{ matrix.key }}
92+
path: apps/${{ matrix.app }}/dist/
93+
retention-days: 7
94+
if-no-files-found: error
95+
96+
trigger:
97+
if: always()
98+
needs: [config, build]
99+
runs-on: ubuntu-latest
100+
steps:
101+
- uses: actions/checkout@v6
102+
with:
103+
sparse-checkout: .github/actions
104+
105+
- name: Generate App token (cross-repo dispatch)
106+
id: app-token
107+
if: needs.build.result == 'success'
108+
uses: actions/create-github-app-token@v3
109+
with:
110+
client-id: ${{ vars.DEPLOY_APP_CLIENT_ID }}
111+
private-key: ${{ secrets.DEPLOY_APP_PRIVATE_KEY }}
112+
owner: ${{ github.repository_owner }}
113+
repositories: ${{ secrets.INFRA_REPO_NAME }}
114+
115+
- name: Dispatch deploy-frontend to infrastructure
116+
if: needs.build.result == 'success'
117+
env:
118+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
119+
INFRA_REPO: ${{ github.repository_owner }}/${{ secrets.INFRA_REPO_NAME }}
120+
PHASE: ${{ needs.config.outputs.phase }}
121+
KEYS: ${{ needs.config.outputs.keys }}
122+
run: |
123+
set -euo pipefail
124+
# Build payload via jq so `keys` stays a real JSON array (gh api -f
125+
# would coerce values to strings).
126+
jq -n \
127+
--arg phase "$PHASE" \
128+
--arg src "${{ github.repository }}" \
129+
--arg run "${{ github.run_id }}" \
130+
--argjson keys "$KEYS" \
131+
'{event_type:"deploy-frontend",client_payload:{phase:$phase,source_repo:$src,source_run_id:$run,keys:$keys}}' \
132+
| gh api "repos/$INFRA_REPO/dispatches" --input -
133+
134+
# If build failed/cancelled, dispatch is skipped — fail this job so
135+
# job.status reflects the real outcome for the slack step.
136+
- name: Propagate upstream status
137+
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
138+
run: exit 1
139+
140+
- uses: ./.github/actions/notify-slack-deploy
141+
if: always()
142+
with:
143+
slack-token: ${{ secrets.SLACK_BOT_TOKEN }}
144+
slack-channel: ${{ vars.SLACK_DEPLOYMENT_ALERT_CHANNEL }}
145+
header-prefix: "frontend → infrastructure (${{ needs.config.outputs.phase || '?' }})"
146+
section-text: "keys: `${{ needs.config.outputs.keys || '?' }}`\n실제 배포 결과는 infrastructure repo `Deploy frontend` 알림 확인"

.github/workflows/deploy.yml

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -83,67 +83,9 @@ jobs:
8383

8484
- run: aws cloudfront create-invalidation --distribution-id ${{ env[matrix.aws_cloudfront_distribution_key] }} --paths "/*"
8585

86-
# Notify to Slack (Success)
87-
- name: Notify deployment to Slack
88-
if: failure() || cancelled()
89-
uses: slackapi/slack-github-action@v1.26.0
86+
- uses: ./.github/actions/notify-slack-deploy
87+
if: always()
9088
with:
91-
channel-id: ${{ vars.SLACK_DEPLOYMENT_ALERT_CHANNEL }}
92-
payload: |
93-
{
94-
"blocks": [
95-
{
96-
"type": "header",
97-
"text": {
98-
"type": "plain_text",
99-
"text": "${{ steps.info.outputs.repository_name }} ${{ matrix.application }} (${{ env.API_STAGE }}) 배포 실패 :rotating_light: (${{ job.status }})",
100-
"emoji": true
101-
}
102-
},
103-
{
104-
"type": "section",
105-
"text": {"type": "mrkdwn", "text": "GitHub Action 바로가기"},
106-
"accessory": {
107-
"type": "button",
108-
"text": {"type": "plain_text", "text": "${{ github.run_id }}"},
109-
"value": "github_action",
110-
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
111-
"action_id": "button-action"
112-
}
113-
}
114-
]
115-
}
116-
env:
117-
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
118-
119-
# Notify to Slack (Failure)
120-
- name: Notify deployment to Slack
121-
uses: slackapi/slack-github-action@v1.26.0
122-
with:
123-
channel-id: ${{ vars.SLACK_DEPLOYMENT_ALERT_CHANNEL }}
124-
payload: |
125-
{
126-
"blocks": [
127-
{
128-
"type": "header",
129-
"text": {
130-
"type": "plain_text",
131-
"text": "${{ steps.info.outputs.repository_name }} ${{ matrix.application }} (${{ env.API_STAGE }}) 배포 성공 :tada:",
132-
"emoji": true
133-
}
134-
},
135-
{
136-
"type": "section",
137-
"text": {"type": "mrkdwn", "text": "GitHub Action 바로가기"},
138-
"accessory": {
139-
"type": "button",
140-
"text": {"type": "plain_text", "text": "${{ github.run_id }}"},
141-
"value": "github_action",
142-
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
143-
"action_id": "button-action"
144-
}
145-
}
146-
]
147-
}
148-
env:
149-
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
89+
slack-token: ${{ secrets.SLACK_BOT_TOKEN }}
90+
slack-channel: ${{ vars.SLACK_DEPLOYMENT_ALERT_CHANNEL }}
91+
header-prefix: "${{ steps.info.outputs.repository_name }} ${{ matrix.application }} (${{ env.API_STAGE }}) 배포"

0 commit comments

Comments
 (0)