project-auto-wizard 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/bin/project-auto-wizard.js +19 -0
- package/package.json +36 -0
- package/payload/coderabbit.yaml +19 -0
- package/payload/config/breaking-changes.json +1 -0
- package/payload/config/wizard-prompts.yml +62 -0
- package/payload/scripts/changelog_manager.py +726 -0
- package/payload/scripts/version_manager.py +617 -0
- package/payload/version.yml.template +48 -0
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +303 -0
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +293 -0
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +289 -0
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +192 -0
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +209 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +591 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +700 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +308 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +992 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +689 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +987 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +471 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml +500 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +185 -0
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +271 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +81 -0
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +2191 -0
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +386 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +194 -0
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +255 -0
- package/payload/workflows/spring/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +84 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +316 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +58 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +535 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +437 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +2277 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +425 -0
- package/src/cli/args.js +95 -0
- package/src/cli/help.js +27 -0
- package/src/commands/full.js +53 -0
- package/src/commands/interactive.js +276 -0
- package/src/commands/revert.js +60 -0
- package/src/commands/version.js +31 -0
- package/src/commands/workflows.js +49 -0
- package/src/context.js +26 -0
- package/src/core/assets.js +47 -0
- package/src/core/branches.js +60 -0
- package/src/core/branding.js +15 -0
- package/src/core/breaking-check.js +65 -0
- package/src/core/breaking.js +26 -0
- package/src/core/copy/coderabbit.js +26 -0
- package/src/core/copy/gitignore.js +55 -0
- package/src/core/copy/readme.js +30 -0
- package/src/core/copy/simple.js +23 -0
- package/src/core/copy/workflows.js +233 -0
- package/src/core/detect-fs.js +106 -0
- package/src/core/detect.js +62 -0
- package/src/core/fsutil.js +46 -0
- package/src/core/options-ask.js +99 -0
- package/src/core/paths-resolve.js +261 -0
- package/src/core/paths.js +16 -0
- package/src/core/version-yml.js +190 -0
- package/src/core/wizard-env.js +101 -0
- package/src/core/wizard-labels.js +107 -0
- package/src/index.js +162 -0
- package/src/ui/ansi.js +26 -0
- package/src/ui/banner.js +29 -0
- package/src/ui/env-plan.js +207 -0
- package/src/ui/prompts.js +99 -0
- package/src/ui/readline-engine.js +257 -0
- package/src/ui/status-cards.js +56 -0
- package/src/ui/summary.js +127 -0
|
@@ -0,0 +1,2277 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# Spring Boot PR/Issue Preview μμ€ν
(SSH + Docker)
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# π νμ GitHub Secrets
|
|
6
|
+
# ===================================================================
|
|
7
|
+
# APPLICATION_PROD_YML (μ ν): application-prod.yml λ΄μ©
|
|
8
|
+
# DOCKERHUB_USERNAME: DockerHub μ¬μ©μλͺ
|
|
9
|
+
# DOCKERHUB_TOKEN: DockerHub μ‘μΈμ€ ν ν°
|
|
10
|
+
# SERVER_HOST: λ°°ν¬ μλ² μ£Όμ
|
|
11
|
+
# SERVER_USER: SSH μ¬μ©μλͺ
|
|
12
|
+
# SERVER_PASSWORD: SSH λΉλ°λ²νΈ (SSH_AUTH_METHOD=passwordμΌ λ)
|
|
13
|
+
# SSH_KEY: SSH κ°μΈν€ .pem λ΄μ© (SSH_AUTH_METHOD=keyμΌ λ, AWS EC2 λ±)
|
|
14
|
+
# ===================================================================
|
|
15
|
+
#
|
|
16
|
+
# π μ¬μ©λ²:
|
|
17
|
+
# @suh-lab server build - PR/Issue λΉλ λ° λ°°ν¬
|
|
18
|
+
# @suh-lab server destroy - Preview νκ²½ μμ
|
|
19
|
+
# @suh-lab server status - νμ¬ μν νμΈ
|
|
20
|
+
#
|
|
21
|
+
# π 리μμ€ λ€μ΄λ° κ·μΉ:
|
|
22
|
+
# - 컨ν
μ΄λ: {PROJECT_NAME}-pr-{PRλ²νΈ}
|
|
23
|
+
# - μ΄λ―Έμ§: {DOCKERHUB_USERNAME}/{PROJECT_NAME}:pr-{PRλ²νΈ}
|
|
24
|
+
# - λλ©μΈ: {PROJECT_NAME}-pr-{PRλ²νΈ}.pr.suhsaechan.kr
|
|
25
|
+
# - Preview URL: http://{λλ©μΈ}:8079
|
|
26
|
+
#
|
|
27
|
+
# ===================================================================
|
|
28
|
+
|
|
29
|
+
name: PROJECT-SPRING-PR-PREVIEW
|
|
30
|
+
|
|
31
|
+
# ===================================================================
|
|
32
|
+
# β οΈ [μμ 1] νλ‘μ νΈλ³ μ€μ - λ€λ₯Έ νλ‘μ νΈμμ μ¬μ© μ μ΄ μΉμ
λ§ μμ νμΈμ
|
|
33
|
+
# ===================================================================
|
|
34
|
+
env:
|
|
35
|
+
# νλ‘μ νΈ κ³ μ μλ³μ (컨ν
μ΄λλͺ
, μ΄λ―Έμ§λͺ
, λλ©μΈμ μ¬μ©) (νλ‘μ νΈλ³ μμ νμ)
|
|
36
|
+
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
37
|
+
|
|
38
|
+
# Spring Boot λΉλ μ€μ (νλ‘μ νΈλ³ μμ νμ)
|
|
39
|
+
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:21
|
|
40
|
+
GRADLE_BUILD_CMD: './gradlew clean build -x test -Dspring.profiles.active=prod'
|
|
41
|
+
JAR_PATH: 'Suh-Web/build/libs/*.jar' # λ©ν°λͺ¨λμ΄λ©΄ λΉλ μ°μΆλ¬Ό κ²½λ‘λ₯Ό μ§μ μμ νμΈμ
|
|
42
|
+
APPLICATION_YML_PATH: "__APPLICATION_YML_PATH__" # @wizard auto:spring-app-yml-path
|
|
43
|
+
|
|
44
|
+
# Docker μ€μ (νλ‘μ νΈλ³ μμ νμ)
|
|
45
|
+
DOCKERFILE_PATH: './Dockerfile'
|
|
46
|
+
INTERNAL_PORT: '8080'
|
|
47
|
+
|
|
48
|
+
# ποΈ λ³Όλ₯¨ λ§μ΄νΈ μ€μ (νλ‘μ νΈλ³ μμ νμ, λΉκ°μ΄λ©΄ λ§μ΄νΈ μν¨)
|
|
49
|
+
PROJECT_TARGET_DIR: "" # μλ²μ λ°μ΄ν° λλ ν 리 (μ: /volume1/projects/myapp/data)
|
|
50
|
+
PROJECT_MNT_DIR: "" # 컨ν
μ΄λ λ΄λΆ λ§μ΄νΈ κ²½λ‘ (μ: /mnt/myapp)
|
|
51
|
+
|
|
52
|
+
# Traefik & Preview λλ©μΈ μ€μ (νκ²½ κ΅¬μΆ ν μμ κΈμ§)
|
|
53
|
+
TRAEFIK_NETWORK: traefik-network
|
|
54
|
+
PREVIEW_DOMAIN_SUFFIX: pr.suhsaechan.kr
|
|
55
|
+
PREVIEW_PORT: '8079'
|
|
56
|
+
|
|
57
|
+
# π SSH μΈμ¦ λ°©μ: "password"(SynologyΒ·μΌλ° μλ²) | "key"(AWS EC2Β·.pem)
|
|
58
|
+
# password β SERVER_PASSWORD secret μ¬μ©
|
|
59
|
+
# key β SSH_KEY secret μ¬μ© (.pem νμΌ μ 체 λ΄μ©μ secret κ°μΌλ‘)
|
|
60
|
+
SSH_AUTH_METHOD: "password" # @wizard ask:password
|
|
61
|
+
|
|
62
|
+
# SSH ν¬νΈ (μλ² ν¬νΈ νκ²½ κ΅¬μΆ ν μμ κΈμ§)
|
|
63
|
+
SSH_PORT: '2022'
|
|
64
|
+
|
|
65
|
+
# Issue Helper λκΈ λ§μ»€ (νλ‘μ νΈλ³ μμ νμ)
|
|
66
|
+
# Issue λκΈμμ λΈλμΉλͺ
μ μΆμΆν λ μ¬μ©
|
|
67
|
+
ISSUE_HELPER_MARKER: 'Guide by SUH-LAB'
|
|
68
|
+
|
|
69
|
+
# Health Check μ€μ (νλ‘μ νΈλ³ μμ νμ)
|
|
70
|
+
# - HEALTH_CHECK_PATH: HTTP Health Check κ²½λ‘ (λΉκ°μ΄λ©΄ HTTP μ²΄ν¬ κ±΄λλ)
|
|
71
|
+
# - HEALTH_CHECK_LOG_PATTERN: λ‘κ·Έ ν¨ν΄ λ§€μΉ (HTTP μ€ν¨ μ ν΄λ°±)
|
|
72
|
+
# - API_DOCS_PATH: API λ¬Έμ URL (λΉκ°μ΄λ©΄ λ°°ν¬ μ½λ©νΈμ λ―Ένμ)
|
|
73
|
+
#
|
|
74
|
+
# Spring Boot κΈ°λ³Έκ°:
|
|
75
|
+
# HEALTH_CHECK_PATH: '/actuator/health'
|
|
76
|
+
# HEALTH_CHECK_LOG_PATTERN: 'Started .* in [0-9.]+ seconds'
|
|
77
|
+
# API_DOCS_PATH: '/swagger-ui.html' λλ '/swagger-ui/index.html'
|
|
78
|
+
HEALTH_CHECK_PATH: '/actuator/health'
|
|
79
|
+
HEALTH_CHECK_LOG_PATTERN: 'Started .* in [0-9.]+ seconds'
|
|
80
|
+
API_DOCS_PATH: '/docs/swagger'
|
|
81
|
+
|
|
82
|
+
# ===================================================================
|
|
83
|
+
# νΈλ¦¬κ±° μ€μ
|
|
84
|
+
# ===================================================================
|
|
85
|
+
on:
|
|
86
|
+
issue_comment:
|
|
87
|
+
types: [created] # PR λκΈ + Issue λκΈ
|
|
88
|
+
issues:
|
|
89
|
+
types: [closed] # Issue λ«ν μ destroy
|
|
90
|
+
pull_request:
|
|
91
|
+
types: [closed] # PR λ«ν μ destroy
|
|
92
|
+
|
|
93
|
+
permissions:
|
|
94
|
+
contents: read
|
|
95
|
+
pull-requests: write
|
|
96
|
+
issues: write
|
|
97
|
+
|
|
98
|
+
# ===================================================================
|
|
99
|
+
# Jobs
|
|
100
|
+
# ===================================================================
|
|
101
|
+
jobs:
|
|
102
|
+
# -----------------------------------------------------------------
|
|
103
|
+
# Job 1: λͺ
λ Ήμ΄ νμ± (PR λκΈ + Issue λκΈ λͺ¨λ μ§μ)
|
|
104
|
+
# -----------------------------------------------------------------
|
|
105
|
+
check-command:
|
|
106
|
+
name: λͺ
λ Ήμ΄ νμΈ
|
|
107
|
+
if: github.event_name == 'issue_comment'
|
|
108
|
+
runs-on: ubuntu-latest
|
|
109
|
+
outputs:
|
|
110
|
+
command: ${{ steps.parse.outputs.command }}
|
|
111
|
+
is_valid: ${{ steps.parse.outputs.is_valid }}
|
|
112
|
+
is_pr: ${{ steps.parse.outputs.is_pr }}
|
|
113
|
+
custom_branch: ${{ steps.parse.outputs.custom_branch }}
|
|
114
|
+
is_custom_branch: ${{ steps.parse.outputs.is_custom_branch }}
|
|
115
|
+
steps:
|
|
116
|
+
- name: λκΈμ π 리μ‘μ
μΆκ°
|
|
117
|
+
if: contains(github.event.comment.body, '@suh-lab') && contains(github.event.comment.body, 'server')
|
|
118
|
+
uses: actions/github-script@v7
|
|
119
|
+
with:
|
|
120
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
121
|
+
script: |
|
|
122
|
+
await github.rest.reactions.createForIssueComment({
|
|
123
|
+
owner: context.repo.owner,
|
|
124
|
+
repo: context.repo.repo,
|
|
125
|
+
comment_id: context.payload.comment.id,
|
|
126
|
+
content: 'eyes'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
- name: 컀맨λ νμ±
|
|
130
|
+
id: parse
|
|
131
|
+
env:
|
|
132
|
+
COMMENT: ${{ github.event.comment.body }}
|
|
133
|
+
IS_PR: ${{ github.event.issue.pull_request != null }}
|
|
134
|
+
run: |
|
|
135
|
+
# PRμΈμ§ IssueμΈμ§ νμΈ
|
|
136
|
+
if [[ "$IS_PR" == "true" ]]; then
|
|
137
|
+
echo "is_pr=true" >> $GITHUB_OUTPUT
|
|
138
|
+
echo "βΉοΈ PR λκΈμμ λͺ
λ Ήμ΄ κ°μ§"
|
|
139
|
+
else
|
|
140
|
+
echo "is_pr=false" >> $GITHUB_OUTPUT
|
|
141
|
+
echo "βΉοΈ Issue λκΈμμ λͺ
λ Ήμ΄ κ°μ§"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# λͺ
λ Ήμ΄ νμ± (λΈλμΉ νλΌλ―Έν° μ§μ)
|
|
145
|
+
# νμ: @suh-lab server <command> [λΈλμΉλͺ
]
|
|
146
|
+
if [[ "$COMMENT" =~ @suh-lab[[:space:]]+server[[:space:]]+build([[:space:]]+([^[:space:]]+))? ]]; then
|
|
147
|
+
echo "command=build" >> $GITHUB_OUTPUT
|
|
148
|
+
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
149
|
+
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
150
|
+
echo "custom_branch=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
|
151
|
+
echo "is_custom_branch=true" >> $GITHUB_OUTPUT
|
|
152
|
+
echo "β
λͺ
λ Ήμ΄ κ°μ§: build (λΈλμΉ: ${BASH_REMATCH[2]})"
|
|
153
|
+
else
|
|
154
|
+
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
155
|
+
echo "β
λͺ
λ Ήμ΄ κ°μ§: build"
|
|
156
|
+
fi
|
|
157
|
+
elif [[ "$COMMENT" =~ @suh-lab[[:space:]]+server[[:space:]]+destroy([[:space:]]+([^[:space:]]+))? ]]; then
|
|
158
|
+
echo "command=destroy" >> $GITHUB_OUTPUT
|
|
159
|
+
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
160
|
+
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
161
|
+
echo "custom_branch=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
|
162
|
+
echo "is_custom_branch=true" >> $GITHUB_OUTPUT
|
|
163
|
+
echo "β
λͺ
λ Ήμ΄ κ°μ§: destroy (λΈλμΉ: ${BASH_REMATCH[2]})"
|
|
164
|
+
else
|
|
165
|
+
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
166
|
+
echo "β
λͺ
λ Ήμ΄ κ°μ§: destroy"
|
|
167
|
+
fi
|
|
168
|
+
elif [[ "$COMMENT" =~ @suh-lab[[:space:]]+server[[:space:]]+status([[:space:]]+([^[:space:]]+))? ]]; then
|
|
169
|
+
echo "command=status" >> $GITHUB_OUTPUT
|
|
170
|
+
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
171
|
+
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
172
|
+
echo "custom_branch=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
|
173
|
+
echo "is_custom_branch=true" >> $GITHUB_OUTPUT
|
|
174
|
+
echo "β
λͺ
λ Ήμ΄ κ°μ§: status (λΈλμΉ: ${BASH_REMATCH[2]})"
|
|
175
|
+
else
|
|
176
|
+
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
177
|
+
echo "β
λͺ
λ Ήμ΄ κ°μ§: status"
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
echo "is_valid=false" >> $GITHUB_OUTPUT
|
|
181
|
+
echo "βΉοΈ @suh-lab server λͺ
λ Ήμ΄κ° μλ"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# -----------------------------------------------------------------
|
|
185
|
+
# Job 2-1: λΉλ & λ°°ν¬ (PR λκΈμμ μ€ν)
|
|
186
|
+
# -----------------------------------------------------------------
|
|
187
|
+
build-preview-pr:
|
|
188
|
+
name: Preview λΉλ & λ°°ν¬ (PR)
|
|
189
|
+
needs: check-command
|
|
190
|
+
if: |
|
|
191
|
+
needs.check-command.outputs.is_valid == 'true' &&
|
|
192
|
+
needs.check-command.outputs.command == 'build' &&
|
|
193
|
+
needs.check-command.outputs.is_pr == 'true' &&
|
|
194
|
+
needs.check-command.outputs.is_custom_branch != 'true'
|
|
195
|
+
runs-on: ubuntu-latest
|
|
196
|
+
steps:
|
|
197
|
+
- name: PR μ 보 κ°μ Έμ€κΈ°
|
|
198
|
+
id: pr
|
|
199
|
+
uses: actions/github-script@v7
|
|
200
|
+
with:
|
|
201
|
+
script: |
|
|
202
|
+
const pr = await github.rest.pulls.get({
|
|
203
|
+
owner: context.repo.owner,
|
|
204
|
+
repo: context.repo.repo,
|
|
205
|
+
pull_number: context.issue.number
|
|
206
|
+
});
|
|
207
|
+
core.setOutput('ref', pr.data.head.ref);
|
|
208
|
+
core.setOutput('sha', pr.data.head.sha.substring(0, 7));
|
|
209
|
+
return pr.data.number;
|
|
210
|
+
|
|
211
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
212
|
+
# μ§ν μν© λκΈ μμ€ν
|
|
213
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
214
|
+
- name: μ§ν μν© λκΈ μμ±
|
|
215
|
+
id: progress
|
|
216
|
+
uses: actions/github-script@v7
|
|
217
|
+
with:
|
|
218
|
+
script: |
|
|
219
|
+
const prNumber = context.issue.number;
|
|
220
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
221
|
+
const startTime = Date.now();
|
|
222
|
+
|
|
223
|
+
const body = [
|
|
224
|
+
'## π PR Preview λΉλ μ€...',
|
|
225
|
+
'',
|
|
226
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
227
|
+
'|------|------|----------|',
|
|
228
|
+
'| π¨ νλ‘μ νΈ λΉλ | β³ μ§ν μ€... | - |',
|
|
229
|
+
'| π³ Docker μ΄λ―Έμ§ μμ± | βΈοΈ λκΈ° | - |',
|
|
230
|
+
'| π μλ² λ°°ν¬ & Health Check | βΈοΈ λκΈ° | - |',
|
|
231
|
+
'',
|
|
232
|
+
`**[π μ€μκ° λ‘κ·Έ 보기](${runUrl})**`,
|
|
233
|
+
'',
|
|
234
|
+
'---',
|
|
235
|
+
'*π€ μ΄ λκΈμ μλμΌλ‘ μ
λ°μ΄νΈλ©λλ€.*'
|
|
236
|
+
].join('\n');
|
|
237
|
+
|
|
238
|
+
const { data: comment } = await github.rest.issues.createComment({
|
|
239
|
+
owner: context.repo.owner,
|
|
240
|
+
repo: context.repo.repo,
|
|
241
|
+
issue_number: prNumber,
|
|
242
|
+
body: body
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
core.setOutput('comment_id', comment.id);
|
|
246
|
+
core.setOutput('start_time', startTime);
|
|
247
|
+
|
|
248
|
+
- name: μ½λ 체ν¬μμ
|
|
249
|
+
uses: actions/checkout@v5
|
|
250
|
+
with:
|
|
251
|
+
ref: ${{ steps.pr.outputs.ref }}
|
|
252
|
+
|
|
253
|
+
- name: JDK μ€μ
|
|
254
|
+
uses: actions/setup-java@v4
|
|
255
|
+
with:
|
|
256
|
+
distribution: 'temurin'
|
|
257
|
+
java-version: ${{ env.JAVA_VERSION }}
|
|
258
|
+
cache: 'gradle'
|
|
259
|
+
|
|
260
|
+
- name: Gradle κΆν μ€μ
|
|
261
|
+
run: chmod +x gradlew
|
|
262
|
+
|
|
263
|
+
# =================================================================
|
|
264
|
+
# β οΈ [μμ 2] νλ‘μ νΈλ³ Secret νμΌ μμ±
|
|
265
|
+
# =================================================================
|
|
266
|
+
#
|
|
267
|
+
# π νμ (λͺ¨λ Spring Boot νλ‘μ νΈ):
|
|
268
|
+
# - application-prod.yml
|
|
269
|
+
#
|
|
270
|
+
# π μ ν (νλ‘μ νΈμ λ°λΌ μΆκ°/μ κ±°):
|
|
271
|
+
# - Firebase: firebase-admin-sdk.json, firebase-messaging-sw.js
|
|
272
|
+
# - Vertex AI: gen-lang-client-xxx.json
|
|
273
|
+
# - κΈ°ν μ€μ νμΌ: admin.yml, prompt.yml λ±
|
|
274
|
+
#
|
|
275
|
+
# β οΈ λ€λ₯Έ νλ‘μ νΈμμ μ¬μ© μ:
|
|
276
|
+
# 1. νμ μλ stepμ μμ νμΈμ
|
|
277
|
+
# 2. νμν stepμ μΆκ°νμΈμ
|
|
278
|
+
# 3. νμΌ κ²½λ‘λ₯Ό νλ‘μ νΈμ λ§κ² μμ νμΈμ
|
|
279
|
+
# =================================================================
|
|
280
|
+
|
|
281
|
+
# [νμ] Spring Boot μ€μ νμΌ
|
|
282
|
+
- name: "[νμ] application-prod.yml μμ±"
|
|
283
|
+
run: |
|
|
284
|
+
mkdir -p $(dirname ${{ env.APPLICATION_YML_PATH }})
|
|
285
|
+
cat << 'EOF' > ${{ env.APPLICATION_YML_PATH }}
|
|
286
|
+
${{ secrets.APPLICATION_PROD_YML }}
|
|
287
|
+
EOF
|
|
288
|
+
|
|
289
|
+
# [μ ν] μλλ μμμ
λλ€. νμ μμΌλ©΄ μμ νμΈμ.
|
|
290
|
+
# - name: "[μ ν] Vertex AI Service Account Key μμ±"
|
|
291
|
+
# env:
|
|
292
|
+
# VERTEX_SA_KEY: ${{ secrets.VERTEX_SA_KEY }}
|
|
293
|
+
# run: |
|
|
294
|
+
# echo "$VERTEX_SA_KEY" | sed 's/\\n/\n/g' > ./Suh-Web/src/main/resources/vertex-ai-key.json
|
|
295
|
+
|
|
296
|
+
# - name: "[μ ν] Firebase Admin SDK μμ±"
|
|
297
|
+
# env:
|
|
298
|
+
# FIREBASE_KEY_JSON: ${{ secrets.FIREBASE_KEY_JSON }}
|
|
299
|
+
# run: |
|
|
300
|
+
# echo "$FIREBASE_KEY_JSON" | sed 's/\\n/\n/g' > ./Suh-Web/src/main/resources/firebase-admin-sdk.json
|
|
301
|
+
|
|
302
|
+
# =================================================================
|
|
303
|
+
# β οΈ [μμ 2 λ] νλ‘μ νΈλ³ Secret νμΌ μμ± λ
|
|
304
|
+
# =================================================================
|
|
305
|
+
|
|
306
|
+
- name: Gradle λΉλ
|
|
307
|
+
run: ${{ env.GRADLE_BUILD_CMD }}
|
|
308
|
+
|
|
309
|
+
- name: μ§ν μν© - λΉλ μλ£
|
|
310
|
+
id: build_progress
|
|
311
|
+
uses: actions/github-script@v7
|
|
312
|
+
with:
|
|
313
|
+
script: |
|
|
314
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
315
|
+
const startTime = ${{ steps.progress.outputs.start_time }};
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
const elapsed = now - startTime;
|
|
318
|
+
const minutes = Math.floor(elapsed / 60000);
|
|
319
|
+
const seconds = Math.floor((elapsed % 60000) / 1000);
|
|
320
|
+
const buildDuration = minutes > 0 ? `${minutes}λΆ ${seconds}μ΄` : `${seconds}μ΄`;
|
|
321
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
322
|
+
|
|
323
|
+
const body = [
|
|
324
|
+
'## π PR Preview λΉλ μ€...',
|
|
325
|
+
'',
|
|
326
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
327
|
+
'|------|------|----------|',
|
|
328
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
329
|
+
'| π³ Docker μ΄λ―Έμ§ μμ± | β³ μ§ν μ€... | - |',
|
|
330
|
+
'| π μλ² λ°°ν¬ & Health Check | βΈοΈ λκΈ° | - |',
|
|
331
|
+
'',
|
|
332
|
+
`**[π μ€μκ° λ‘κ·Έ 보기](${runUrl})**`,
|
|
333
|
+
'',
|
|
334
|
+
'---',
|
|
335
|
+
'*π€ μ΄ λκΈμ μλμΌλ‘ μ
λ°μ΄νΈλ©λλ€.*'
|
|
336
|
+
].join('\n');
|
|
337
|
+
|
|
338
|
+
await github.rest.issues.updateComment({
|
|
339
|
+
owner: context.repo.owner,
|
|
340
|
+
repo: context.repo.repo,
|
|
341
|
+
comment_id: commentId,
|
|
342
|
+
body: body
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
core.setOutput('build_duration', buildDuration);
|
|
346
|
+
core.setOutput('docker_start', now);
|
|
347
|
+
|
|
348
|
+
- name: Docker λ‘κ·ΈμΈ
|
|
349
|
+
uses: docker/login-action@v3
|
|
350
|
+
with:
|
|
351
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
352
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
353
|
+
|
|
354
|
+
- name: Docker Buildx μ€μ
|
|
355
|
+
uses: docker/setup-buildx-action@v3
|
|
356
|
+
|
|
357
|
+
- name: Docker μ΄λ―Έμ§ λΉλ & Push
|
|
358
|
+
uses: docker/build-push-action@v5
|
|
359
|
+
with:
|
|
360
|
+
context: .
|
|
361
|
+
file: ${{ env.DOCKERFILE_PATH }}
|
|
362
|
+
push: true
|
|
363
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:pr-${{ github.event.issue.number }}
|
|
364
|
+
cache-from: type=gha
|
|
365
|
+
cache-to: type=gha,mode=max
|
|
366
|
+
|
|
367
|
+
- name: μ§ν μν© - Docker μλ£
|
|
368
|
+
id: docker_progress
|
|
369
|
+
uses: actions/github-script@v7
|
|
370
|
+
with:
|
|
371
|
+
script: |
|
|
372
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
373
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
374
|
+
const dockerStart = ${{ steps.build_progress.outputs.docker_start }};
|
|
375
|
+
const now = Date.now();
|
|
376
|
+
const dockerElapsed = now - dockerStart;
|
|
377
|
+
const minutes = Math.floor(dockerElapsed / 60000);
|
|
378
|
+
const seconds = Math.floor((dockerElapsed % 60000) / 1000);
|
|
379
|
+
const dockerDuration = minutes > 0 ? `${minutes}λΆ ${seconds}μ΄` : `${seconds}μ΄`;
|
|
380
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
381
|
+
|
|
382
|
+
const body = [
|
|
383
|
+
'## π PR Preview λΉλ μ€...',
|
|
384
|
+
'',
|
|
385
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
386
|
+
'|------|------|----------|',
|
|
387
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
388
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | β
μλ£ | ${dockerDuration} |`,
|
|
389
|
+
'| π μλ² λ°°ν¬ & Health Check | β³ μ§ν μ€... | - |',
|
|
390
|
+
'',
|
|
391
|
+
`**[π μ€μκ° λ‘κ·Έ 보기](${runUrl})**`,
|
|
392
|
+
'',
|
|
393
|
+
'---',
|
|
394
|
+
'*π€ μ΄ λκΈμ μλμΌλ‘ μ
λ°μ΄νΈλ©λλ€.*'
|
|
395
|
+
].join('\n');
|
|
396
|
+
|
|
397
|
+
await github.rest.issues.updateComment({
|
|
398
|
+
owner: context.repo.owner,
|
|
399
|
+
repo: context.repo.repo,
|
|
400
|
+
comment_id: commentId,
|
|
401
|
+
body: body
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
core.setOutput('build_duration', buildDuration);
|
|
405
|
+
core.setOutput('docker_duration', dockerDuration);
|
|
406
|
+
core.setOutput('deploy_start', now);
|
|
407
|
+
|
|
408
|
+
- name: μλ²μ λ°°ν¬
|
|
409
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
410
|
+
with:
|
|
411
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
412
|
+
username: ${{ secrets.SERVER_USER }}
|
|
413
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
414
|
+
key: ${{ secrets.SSH_KEY }}
|
|
415
|
+
port: ${{ env.SSH_PORT }}
|
|
416
|
+
script: |
|
|
417
|
+
set -e
|
|
418
|
+
|
|
419
|
+
# νκ²½ λ³μ μ€μ (λ°°ν¬ μλ²μ©)
|
|
420
|
+
export PATH=$PATH:/usr/local/bin
|
|
421
|
+
export PW="${{ secrets.SERVER_PASSWORD }}"
|
|
422
|
+
|
|
423
|
+
# π SSH μΈμ¦ λ°©μμ λ°λ₯Έ sudo μΆμν
|
|
424
|
+
SSH_AUTH_METHOD="${{ env.SSH_AUTH_METHOD }}"
|
|
425
|
+
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
426
|
+
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
427
|
+
SUDO() { sudo "$@"; }
|
|
428
|
+
else
|
|
429
|
+
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
430
|
+
fi
|
|
431
|
+
echo "π SSH μΈμ¦ λ°©μ: ${SSH_AUTH_METHOD}"
|
|
432
|
+
|
|
433
|
+
# λ³μ μ€μ
|
|
434
|
+
PR_NUMBER=${{ github.event.issue.number }}
|
|
435
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
436
|
+
CONTAINER_NAME="${PROJECT_NAME}-pr-${PR_NUMBER}"
|
|
437
|
+
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
|
|
438
|
+
DOMAIN="${PROJECT_NAME}-pr-${PR_NUMBER}.${{ env.PREVIEW_DOMAIN_SUFFIX }}"
|
|
439
|
+
INTERNAL_PORT="${{ env.INTERNAL_PORT }}"
|
|
440
|
+
TRAEFIK_NETWORK="${{ env.TRAEFIK_NETWORK }}"
|
|
441
|
+
|
|
442
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
443
|
+
echo "π PR Preview λ°°ν¬ μμ"
|
|
444
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
445
|
+
echo "π¦ νλ‘μ νΈ: ${PROJECT_NAME}"
|
|
446
|
+
echo "π’ PR λ²νΈ: #${PR_NUMBER}"
|
|
447
|
+
echo "π 컨ν
μ΄λ: ${CONTAINER_NAME}"
|
|
448
|
+
echo "π λλ©μΈ: ${DOMAIN}"
|
|
449
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
450
|
+
|
|
451
|
+
# μ΄λ―Έμ§ Pull
|
|
452
|
+
echo "π₯ Docker μ΄λ―Έμ§ Pull μ€..."
|
|
453
|
+
SUDO docker pull "${IMAGE}"
|
|
454
|
+
|
|
455
|
+
# κΈ°μ‘΄ 컨ν
μ΄λ μμ
|
|
456
|
+
echo "ποΈ κΈ°μ‘΄ 컨ν
μ΄λ μ 리 μ€..."
|
|
457
|
+
SUDO docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
458
|
+
|
|
459
|
+
# λ³Όλ₯¨ λ§μ΄νΈ μ΅μ
ꡬμ±
|
|
460
|
+
VOLUME_OPTS="-v /etc/localtime:/etc/localtime:ro"
|
|
461
|
+
if [ -n "${{ env.PROJECT_TARGET_DIR }}" ] && [ -n "${{ env.PROJECT_MNT_DIR }}" ]; then
|
|
462
|
+
VOLUME_OPTS="$VOLUME_OPTS -v ${{ env.PROJECT_TARGET_DIR }}:${{ env.PROJECT_MNT_DIR }}"
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
# μ 컨ν
μ΄λ μ€ν
|
|
466
|
+
echo "π³ μ 컨ν
μ΄λ μ€ν μ€..."
|
|
467
|
+
SUDO docker run -d \
|
|
468
|
+
--name "${CONTAINER_NAME}" \
|
|
469
|
+
--network "${TRAEFIK_NETWORK}" \
|
|
470
|
+
--label "traefik.enable=true" \
|
|
471
|
+
--label "traefik.http.routers.${CONTAINER_NAME}.rule=Host(\`${DOMAIN}\`)" \
|
|
472
|
+
--label "traefik.http.routers.${CONTAINER_NAME}.entrypoints=web" \
|
|
473
|
+
--label "traefik.http.services.${CONTAINER_NAME}.loadbalancer.server.port=${INTERNAL_PORT}" \
|
|
474
|
+
-e TZ=Asia/Seoul \
|
|
475
|
+
-e SPRING_PROFILES_ACTIVE=prod \
|
|
476
|
+
$VOLUME_OPTS \
|
|
477
|
+
"${IMAGE}"
|
|
478
|
+
|
|
479
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
480
|
+
# Health Check (μ΅λ 120μ΄ λκΈ°) - νμ΄λΈλ¦¬λ λ°©μ
|
|
481
|
+
# 1. HEALTH_CHECK_PATHκ° μ€μ λμ΄ μμΌλ©΄ HTTP μ²΄ν¬ μλ
|
|
482
|
+
# 2. ν΄λ°±: λ‘κ·Έ ν¨ν΄ λ§€μΉ (HEALTH_CHECK_LOG_PATTERN)
|
|
483
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
484
|
+
echo ""
|
|
485
|
+
echo "β³ Health Check μμ (μ΅λ 120μ΄ λκΈ°)..."
|
|
486
|
+
HEALTH_PATH="${{ env.HEALTH_CHECK_PATH }}"
|
|
487
|
+
LOG_PATTERN="${{ env.HEALTH_CHECK_LOG_PATTERN }}"
|
|
488
|
+
MAX_RETRIES=24
|
|
489
|
+
RETRY_COUNT=0
|
|
490
|
+
HEALTH_CHECK_PASSED=false
|
|
491
|
+
HEALTH_CHECK_METHOD=""
|
|
492
|
+
|
|
493
|
+
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
494
|
+
sleep 5
|
|
495
|
+
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
496
|
+
|
|
497
|
+
# 1. 컨ν
μ΄λ μν νμΈ
|
|
498
|
+
STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${CONTAINER_NAME}" 2>/dev/null || echo "not_found")
|
|
499
|
+
|
|
500
|
+
if [ "$STATUS" = "exited" ]; then
|
|
501
|
+
echo "β 컨ν
μ΄λ λΉμ μ μ’
λ£!"
|
|
502
|
+
echo ""
|
|
503
|
+
echo "π 컨ν
μ΄λ λ‘κ·Έ (μ΅κ·Ό 100μ€):"
|
|
504
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
505
|
+
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
506
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
507
|
+
exit 1
|
|
508
|
+
fi
|
|
509
|
+
|
|
510
|
+
if [ "$STATUS" = "running" ]; then
|
|
511
|
+
# 2. HTTP Health Check μλ (HEALTH_CHECK_PATHκ° μ€μ λ κ²½μ°μλ§)
|
|
512
|
+
if [ -n "$HEALTH_PATH" ]; then
|
|
513
|
+
HEALTH=$(SUDO docker exec "${CONTAINER_NAME}" wget -qO- "http://localhost:${INTERNAL_PORT}${HEALTH_PATH}" 2>/dev/null || echo "")
|
|
514
|
+
|
|
515
|
+
# Spring Actuator μλ΅ νμΈ ({"status":"UP"}) λλ μΌλ° HTTP μλ΅
|
|
516
|
+
if echo "$HEALTH" | grep -q '"status":"UP"' || [ -n "$HEALTH" ]; then
|
|
517
|
+
echo "β
μ μ κΈ°λ νμΈ! (HTTP μλ΅: ${HEALTH_PATH})"
|
|
518
|
+
HEALTH_CHECK_PASSED=true
|
|
519
|
+
HEALTH_CHECK_METHOD="HTTP"
|
|
520
|
+
break
|
|
521
|
+
fi
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
# 3. HTTP μλ΅ μμΌλ©΄ λ‘κ·Έ ν¨ν΄ λ§€μΉμΌλ‘ ν΄λ°±
|
|
525
|
+
if [ -n "$LOG_PATTERN" ]; then
|
|
526
|
+
STARTED=$(SUDO docker logs --tail 50 "${CONTAINER_NAME}" 2>&1 | grep -E "$LOG_PATTERN" || echo "")
|
|
527
|
+
|
|
528
|
+
if [ -n "$STARTED" ]; then
|
|
529
|
+
echo "β
μ μ κΈ°λ νμΈ! (λ‘κ·Έ ν¨ν΄)"
|
|
530
|
+
echo " $STARTED"
|
|
531
|
+
HEALTH_CHECK_PASSED=true
|
|
532
|
+
HEALTH_CHECK_METHOD="Log"
|
|
533
|
+
break
|
|
534
|
+
fi
|
|
535
|
+
fi
|
|
536
|
+
fi
|
|
537
|
+
|
|
538
|
+
echo "β³ λκΈ° μ€... ($RETRY_COUNT/$MAX_RETRIES) - μν: $STATUS"
|
|
539
|
+
done
|
|
540
|
+
|
|
541
|
+
if [ "$HEALTH_CHECK_PASSED" = "false" ]; then
|
|
542
|
+
echo ""
|
|
543
|
+
echo "β Health Check νμμμ (120μ΄)"
|
|
544
|
+
echo ""
|
|
545
|
+
echo "π 컨ν
μ΄λ λ‘κ·Έ (μ΅κ·Ό 100μ€):"
|
|
546
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
547
|
+
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
548
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
549
|
+
exit 1
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
553
|
+
echo "β
λ°°ν¬ λ° Health Check μλ£! (λ°©μ: ${HEALTH_CHECK_METHOD})"
|
|
554
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
555
|
+
|
|
556
|
+
- name: λ°°ν¬ μλ£ μ½λ©νΈ
|
|
557
|
+
uses: actions/github-script@v7
|
|
558
|
+
with:
|
|
559
|
+
script: |
|
|
560
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
561
|
+
const buildDuration = '${{ steps.docker_progress.outputs.build_duration }}';
|
|
562
|
+
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
563
|
+
const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
|
|
564
|
+
const now = Date.now();
|
|
565
|
+
const deployElapsed = now - deployStart;
|
|
566
|
+
const minutes = Math.floor(deployElapsed / 60000);
|
|
567
|
+
const seconds = Math.floor((deployElapsed % 60000) / 1000);
|
|
568
|
+
const deployDuration = minutes > 0 ? `${minutes}λΆ ${seconds}μ΄` : `${seconds}μ΄`;
|
|
569
|
+
|
|
570
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
571
|
+
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
572
|
+
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
573
|
+
const apiDocsPath = '${{ env.API_DOCS_PATH }}';
|
|
574
|
+
const prNumber = context.issue.number;
|
|
575
|
+
const domain = `${projectName}-pr-${prNumber}.${domainSuffix}`;
|
|
576
|
+
const previewUrl = `http://${domain}:${previewPort}`;
|
|
577
|
+
const sha = '${{ steps.pr.outputs.sha }}';
|
|
578
|
+
|
|
579
|
+
// Preview νκ²½ ν
μ΄λΈ κ΅¬μ± (API_DOCS_PATHκ° μμ λλ§ API Docs ν μΆκ°)
|
|
580
|
+
const envRows = [
|
|
581
|
+
`| **Preview URL** | ${previewUrl} |`,
|
|
582
|
+
];
|
|
583
|
+
if (apiDocsPath) {
|
|
584
|
+
envRows.push(`| **API Docs** | ${previewUrl}${apiDocsPath} |`);
|
|
585
|
+
}
|
|
586
|
+
envRows.push(`| **컨ν
μ΄λ** | \`${projectName}-pr-${prNumber}\` |`);
|
|
587
|
+
envRows.push(`| **컀λ°** | \`${sha}\` |`);
|
|
588
|
+
|
|
589
|
+
const branchName = '${{ steps.pr.outputs.ref }}';
|
|
590
|
+
|
|
591
|
+
const body = [
|
|
592
|
+
'## β
PR Preview λ°°ν¬ μλ£!',
|
|
593
|
+
'',
|
|
594
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
595
|
+
'|------|------|----------|',
|
|
596
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
597
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | β
μλ£ | ${dockerDuration} |`,
|
|
598
|
+
`| π μλ² λ°°ν¬ & Health Check | β
μλ£ | ${deployDuration} |`,
|
|
599
|
+
'',
|
|
600
|
+
'### π Preview νκ²½',
|
|
601
|
+
'| νλͺ© | κ° |',
|
|
602
|
+
'|------|-----|',
|
|
603
|
+
...envRows,
|
|
604
|
+
'',
|
|
605
|
+
'### π λͺ
λ Ήμ΄',
|
|
606
|
+
'| λͺ
λ Ήμ΄ | μ€λͺ
|',
|
|
607
|
+
'|--------|------|',
|
|
608
|
+
'| `@suh-lab server build` | μ΅μ 컀λ°μΌλ‘ μ¬λ°°ν¬ |',
|
|
609
|
+
'| `@suh-lab server destroy` | Preview νκ²½ μμ |',
|
|
610
|
+
'| `@suh-lab server status` | νμ¬ μν νμΈ |',
|
|
611
|
+
'',
|
|
612
|
+
'<details>',
|
|
613
|
+
'<summary>π§ κ³ κΈ λͺ
λ Ήμ΄ (λ€λ₯Έ Issue/PRμμ μ μ΄)</summary>',
|
|
614
|
+
'',
|
|
615
|
+
'```',
|
|
616
|
+
`@suh-lab server build ${branchName}`,
|
|
617
|
+
`@suh-lab server destroy ${branchName}`,
|
|
618
|
+
`@suh-lab server status ${branchName}`,
|
|
619
|
+
'```',
|
|
620
|
+
'</details>',
|
|
621
|
+
'',
|
|
622
|
+
'---',
|
|
623
|
+
'*π€ μ΄ λκΈμ PR Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
624
|
+
].join('\n');
|
|
625
|
+
|
|
626
|
+
await github.rest.issues.updateComment({
|
|
627
|
+
owner: context.repo.owner,
|
|
628
|
+
repo: context.repo.repo,
|
|
629
|
+
comment_id: commentId,
|
|
630
|
+
body: body
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
- name: λΉλ/λ°°ν¬ μ€ν¨ μ μλ¬ μ½λ©νΈ
|
|
634
|
+
if: failure()
|
|
635
|
+
uses: actions/github-script@v7
|
|
636
|
+
with:
|
|
637
|
+
script: |
|
|
638
|
+
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
639
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
640
|
+
const prNumber = context.issue.number;
|
|
641
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
642
|
+
|
|
643
|
+
// μ§ν μν©μ λ°λΌ μ΄λμ μ€ν¨νλμ§ νμ
|
|
644
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
645
|
+
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
646
|
+
|
|
647
|
+
// μ€ν¨ μ§μ νλ¨ - μ΄κΈ° λ¨κ³ μ€ν¨λ ꡬλΆ
|
|
648
|
+
let buildStatus, dockerStatus, deployStatus;
|
|
649
|
+
let buildTime = '-', dockerTime = '-';
|
|
650
|
+
|
|
651
|
+
if (!commentId) {
|
|
652
|
+
// μ§ν μν© λκΈ μμ± μ μ€ν¨ (μ΄κΈ°ν λ¨κ³)
|
|
653
|
+
buildStatus = 'β οΈ μ΄κΈ°ν μ€ν¨';
|
|
654
|
+
dockerStatus = '-';
|
|
655
|
+
deployStatus = '-';
|
|
656
|
+
} else if (!buildDuration || buildDuration === '') {
|
|
657
|
+
// λΉλ λ¨κ³μμ μ€ν¨
|
|
658
|
+
buildStatus = 'β μ€ν¨';
|
|
659
|
+
dockerStatus = 'βΈοΈ λκΈ°';
|
|
660
|
+
deployStatus = 'βΈοΈ λκΈ°';
|
|
661
|
+
} else if (!dockerDuration || dockerDuration === '') {
|
|
662
|
+
// Docker λ¨κ³μμ μ€ν¨
|
|
663
|
+
buildStatus = 'β
μλ£';
|
|
664
|
+
dockerStatus = 'β μ€ν¨';
|
|
665
|
+
deployStatus = 'βΈοΈ λκΈ°';
|
|
666
|
+
buildTime = buildDuration;
|
|
667
|
+
} else {
|
|
668
|
+
// λ°°ν¬ λ¨κ³μμ μ€ν¨
|
|
669
|
+
buildStatus = 'β
μλ£';
|
|
670
|
+
dockerStatus = 'β
μλ£';
|
|
671
|
+
deployStatus = 'β μ€ν¨';
|
|
672
|
+
buildTime = buildDuration;
|
|
673
|
+
dockerTime = dockerDuration;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const body = [
|
|
677
|
+
'## β PR Preview λ°°ν¬ μ€ν¨!',
|
|
678
|
+
'',
|
|
679
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
680
|
+
'|------|------|----------|',
|
|
681
|
+
`| π¨ νλ‘μ νΈ λΉλ | ${buildStatus} | ${buildTime} |`,
|
|
682
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | ${dockerStatus} | ${dockerTime} |`,
|
|
683
|
+
`| π μλ² λ°°ν¬ & Health Check | ${deployStatus} | - |`,
|
|
684
|
+
'',
|
|
685
|
+
`**[π λΉλ/λ°°ν¬ λ‘κ·Έ νμΈ](${runUrl})**`,
|
|
686
|
+
'',
|
|
687
|
+
'### π κ°λ₯ν μμΈ',
|
|
688
|
+
'- Gradle λΉλ μ€ν¨',
|
|
689
|
+
'- Docker μ΄λ―Έμ§ λΉλ μ€ν¨',
|
|
690
|
+
'- 컨ν
μ΄λ μμ μ€ν¨ (Spring Boot κΈ°λ μ€λ₯)',
|
|
691
|
+
'- Health Check νμμμ (120μ΄ λ΄ κΈ°λ μλ£ μλ¨)',
|
|
692
|
+
'',
|
|
693
|
+
'### π‘ λ€μ λ¨κ³',
|
|
694
|
+
'1. μ λ§ν¬μμ λΉλ/λ°°ν¬ λ‘κ·Έλ₯Ό νμΈνμΈμ',
|
|
695
|
+
'2. λ¬Έμ λ₯Ό μμ ν ν λ€μ μλνμΈμ: `@suh-lab server build`',
|
|
696
|
+
'',
|
|
697
|
+
'---',
|
|
698
|
+
'*π€ μ΄ λκΈμ PR Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
699
|
+
].join('\n');
|
|
700
|
+
|
|
701
|
+
// μ§ν μν© λκΈμ΄ μμΌλ©΄ μ
λ°μ΄νΈ, μμΌλ©΄ μλ‘ μμ±
|
|
702
|
+
if (commentId) {
|
|
703
|
+
await github.rest.issues.updateComment({
|
|
704
|
+
owner: context.repo.owner,
|
|
705
|
+
repo: context.repo.repo,
|
|
706
|
+
comment_id: commentId,
|
|
707
|
+
body: body
|
|
708
|
+
});
|
|
709
|
+
} else {
|
|
710
|
+
await github.rest.issues.createComment({
|
|
711
|
+
owner: context.repo.owner,
|
|
712
|
+
repo: context.repo.repo,
|
|
713
|
+
issue_number: prNumber,
|
|
714
|
+
body: body
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
# -----------------------------------------------------------------
|
|
719
|
+
# Job 2-2: Issueμμ λΈλμΉλͺ
μΆμΆ
|
|
720
|
+
# -----------------------------------------------------------------
|
|
721
|
+
get-branch-from-issue:
|
|
722
|
+
name: Issueμμ λΈλμΉ μΆμΆ
|
|
723
|
+
needs: check-command
|
|
724
|
+
if: |
|
|
725
|
+
needs.check-command.outputs.is_valid == 'true' &&
|
|
726
|
+
needs.check-command.outputs.is_pr == 'false' &&
|
|
727
|
+
needs.check-command.outputs.is_custom_branch != 'true'
|
|
728
|
+
runs-on: ubuntu-latest
|
|
729
|
+
outputs:
|
|
730
|
+
branch_name: ${{ steps.extract.outputs.branch }}
|
|
731
|
+
found: ${{ steps.extract.outputs.found }}
|
|
732
|
+
issue_number: ${{ steps.extract.outputs.issue_number }}
|
|
733
|
+
steps:
|
|
734
|
+
- name: Issue λκΈμμ λΈλμΉλͺ
μΆμΆ
|
|
735
|
+
id: extract
|
|
736
|
+
uses: actions/github-script@v7
|
|
737
|
+
with:
|
|
738
|
+
script: |
|
|
739
|
+
const issueNumber = context.issue.number;
|
|
740
|
+
const marker = '${{ env.ISSUE_HELPER_MARKER }}';
|
|
741
|
+
|
|
742
|
+
console.log(`π Issue #${issueNumber}μμ λΈλμΉ κ²μ μ€...`);
|
|
743
|
+
console.log(`π λ§μ»€: ${marker}`);
|
|
744
|
+
|
|
745
|
+
// Issueμ λͺ¨λ λκΈ μ‘°ν
|
|
746
|
+
const comments = await github.rest.issues.listComments({
|
|
747
|
+
owner: context.repo.owner,
|
|
748
|
+
repo: context.repo.repo,
|
|
749
|
+
issue_number: issueNumber
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// λΈλμΉ μΆμΆ ν¨ν΄ (λ§μ»€μ 무κ΄νκ² λμΌν νμ)
|
|
753
|
+
// "### λΈλμΉ" λλ "### λΈλμΉλͺ
" λ λ€ λ§€μΉ
|
|
754
|
+
const branchRegex = /###\s*λΈλμΉ(?:λͺ
)?\s*\n```\s*\n([^\n]+)\s*\n```/;
|
|
755
|
+
|
|
756
|
+
for (const comment of comments.data) {
|
|
757
|
+
// λ§μ»€κ° ν¬ν¨λ λκΈ μ°ΎκΈ°
|
|
758
|
+
if (comment.body.includes(marker)) {
|
|
759
|
+
const match = comment.body.match(branchRegex);
|
|
760
|
+
if (match) {
|
|
761
|
+
const branchName = match[1].trim();
|
|
762
|
+
core.setOutput('branch', branchName);
|
|
763
|
+
core.setOutput('found', 'true');
|
|
764
|
+
core.setOutput('issue_number', issueNumber);
|
|
765
|
+
console.log(`β
λΈλμΉ λ°κ²¬: ${branchName}`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// λΈλμΉλ₯Ό μ°Ύμ§ λͺ»ν κ²½μ° - μλ¬κ° μλ, gracefulνκ² μ²λ¦¬
|
|
772
|
+
core.setOutput('found', 'false');
|
|
773
|
+
core.setOutput('issue_number', issueNumber);
|
|
774
|
+
console.log('β οΈ Issue Helper λκΈμμ λΈλμΉλ₯Ό μ°Ύμ μ μμ΅λλ€.');
|
|
775
|
+
|
|
776
|
+
- name: λΈλμΉ μμ μλ¦Ό
|
|
777
|
+
if: steps.extract.outputs.found == 'false'
|
|
778
|
+
uses: actions/github-script@v7
|
|
779
|
+
with:
|
|
780
|
+
script: |
|
|
781
|
+
const issueNumber = context.issue.number;
|
|
782
|
+
const marker = '${{ env.ISSUE_HELPER_MARKER }}';
|
|
783
|
+
|
|
784
|
+
const body = [
|
|
785
|
+
'## β οΈ λΈλμΉλ₯Ό μ°Ύμ μ μμ΅λλ€',
|
|
786
|
+
'',
|
|
787
|
+
'| νλͺ© | κ° |',
|
|
788
|
+
'|------|-----|',
|
|
789
|
+
`| **Issue** | #${issueNumber} |`,
|
|
790
|
+
`| **λ§μ»€** | \`${marker}\` |`,
|
|
791
|
+
'',
|
|
792
|
+
'### π‘ νμΈ μ¬ν',
|
|
793
|
+
'1. Issue Helper λκΈμ΄ μ‘΄μ¬νλμ§ νμΈνμΈμ',
|
|
794
|
+
'2. λκΈμ `### λΈλμΉ` μΉμ
μ΄ μλμ§ νμΈνμΈμ',
|
|
795
|
+
'3. `ISSUE_HELPER_MARKER` νκ²½λ³μκ° μ¬λ°λ₯Έμ§ νμΈνμΈμ',
|
|
796
|
+
'',
|
|
797
|
+
'---',
|
|
798
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
799
|
+
].join('\n');
|
|
800
|
+
|
|
801
|
+
await github.rest.issues.createComment({
|
|
802
|
+
owner: context.repo.owner,
|
|
803
|
+
repo: context.repo.repo,
|
|
804
|
+
issue_number: issueNumber,
|
|
805
|
+
body: body
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
# -----------------------------------------------------------------
|
|
809
|
+
# Job 2-3: λΉλ & λ°°ν¬ (Issue λκΈμμ μ€ν)
|
|
810
|
+
# -----------------------------------------------------------------
|
|
811
|
+
build-preview-issue:
|
|
812
|
+
name: Preview λΉλ & λ°°ν¬ (Issue)
|
|
813
|
+
needs: [check-command, get-branch-from-issue]
|
|
814
|
+
if: |
|
|
815
|
+
needs.check-command.outputs.is_valid == 'true' &&
|
|
816
|
+
needs.check-command.outputs.command == 'build' &&
|
|
817
|
+
needs.check-command.outputs.is_pr == 'false' &&
|
|
818
|
+
needs.check-command.outputs.is_custom_branch != 'true' &&
|
|
819
|
+
needs.get-branch-from-issue.outputs.found == 'true'
|
|
820
|
+
runs-on: ubuntu-latest
|
|
821
|
+
steps:
|
|
822
|
+
- name: λΈλμΉ μ‘΄μ¬ νμΈ
|
|
823
|
+
id: check_branch
|
|
824
|
+
uses: actions/github-script@v7
|
|
825
|
+
with:
|
|
826
|
+
script: |
|
|
827
|
+
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
828
|
+
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
829
|
+
|
|
830
|
+
console.log(`π λΈλμΉ μ‘΄μ¬ νμΈ: ${branchName}`);
|
|
831
|
+
|
|
832
|
+
try {
|
|
833
|
+
await github.rest.repos.getBranch({
|
|
834
|
+
owner: context.repo.owner,
|
|
835
|
+
repo: context.repo.repo,
|
|
836
|
+
branch: branchName
|
|
837
|
+
});
|
|
838
|
+
core.setOutput('exists', 'true');
|
|
839
|
+
console.log(`β
λΈλμΉ μ‘΄μ¬: ${branchName}`);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
if (error.status === 404) {
|
|
842
|
+
core.setOutput('exists', 'false');
|
|
843
|
+
console.log(`β λΈλμΉ μμ: ${branchName}`);
|
|
844
|
+
|
|
845
|
+
// λΈλμΉ μμ μλ¦Ό λκΈ
|
|
846
|
+
const body = [
|
|
847
|
+
'## β λΈλμΉλ₯Ό μ°Ύμ μ μμ΅λλ€',
|
|
848
|
+
'',
|
|
849
|
+
'| νλͺ© | κ° |',
|
|
850
|
+
'|------|-----|',
|
|
851
|
+
`| **Issue** | #${issueNumber} |`,
|
|
852
|
+
`| **λΈλμΉ** | \`${branchName}\` |`,
|
|
853
|
+
'',
|
|
854
|
+
'### π‘ νμΈ μ¬ν',
|
|
855
|
+
'1. λΈλμΉκ° pushλμλμ§ νμΈνμΈμ',
|
|
856
|
+
'2. λΈλμΉλͺ
μ΄ μ ννμ§ νμΈνμΈμ',
|
|
857
|
+
'3. λΈλμΉκ° μμ λμ§ μμλμ§ νμΈνμΈμ',
|
|
858
|
+
'',
|
|
859
|
+
'---',
|
|
860
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
861
|
+
].join('\n');
|
|
862
|
+
|
|
863
|
+
await github.rest.issues.createComment({
|
|
864
|
+
owner: context.repo.owner,
|
|
865
|
+
repo: context.repo.repo,
|
|
866
|
+
issue_number: issueNumber,
|
|
867
|
+
body: body
|
|
868
|
+
});
|
|
869
|
+
} else {
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
- name: λΈλμΉ μμΌλ©΄ μ€λ¨
|
|
875
|
+
if: steps.check_branch.outputs.exists == 'false'
|
|
876
|
+
run: |
|
|
877
|
+
echo "β οΈ λΈλμΉκ° μ‘΄μ¬νμ§ μμ λΉλλ₯Ό 건λλλλ€."
|
|
878
|
+
exit 0
|
|
879
|
+
|
|
880
|
+
- name: Issue μ 보 μ€μ
|
|
881
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
882
|
+
id: issue
|
|
883
|
+
run: |
|
|
884
|
+
echo "ref=${{ needs.get-branch-from-issue.outputs.branch_name }}" >> $GITHUB_OUTPUT
|
|
885
|
+
echo "issue_number=${{ needs.get-branch-from-issue.outputs.issue_number }}" >> $GITHUB_OUTPUT
|
|
886
|
+
# λΈλμΉμ μ΅μ μ»€λ° SHA κ°μ Έμ€κΈ°
|
|
887
|
+
echo "sha=$(git ls-remote https://github.com/${{ github.repository }}.git refs/heads/${{ needs.get-branch-from-issue.outputs.branch_name }} | cut -c1-7)" >> $GITHUB_OUTPUT
|
|
888
|
+
|
|
889
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
890
|
+
# μ§ν μν© λκΈ μμ€ν
|
|
891
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
892
|
+
- name: μ§ν μν© λκΈ μμ±
|
|
893
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
894
|
+
id: progress
|
|
895
|
+
uses: actions/github-script@v7
|
|
896
|
+
with:
|
|
897
|
+
script: |
|
|
898
|
+
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
899
|
+
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
900
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
901
|
+
const startTime = Date.now();
|
|
902
|
+
|
|
903
|
+
const body = [
|
|
904
|
+
'## π Issue Preview λΉλ μ€...',
|
|
905
|
+
'',
|
|
906
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
907
|
+
'',
|
|
908
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
909
|
+
'|------|------|----------|',
|
|
910
|
+
'| π¨ νλ‘μ νΈ λΉλ | β³ μ§ν μ€... | - |',
|
|
911
|
+
'| π³ Docker μ΄λ―Έμ§ μμ± | βΈοΈ λκΈ° | - |',
|
|
912
|
+
'| π μλ² λ°°ν¬ & Health Check | βΈοΈ λκΈ° | - |',
|
|
913
|
+
'',
|
|
914
|
+
`**[π μ€μκ° λ‘κ·Έ 보기](${runUrl})**`,
|
|
915
|
+
'',
|
|
916
|
+
'---',
|
|
917
|
+
'*π€ μ΄ λκΈμ μλμΌλ‘ μ
λ°μ΄νΈλ©λλ€.*'
|
|
918
|
+
].join('\n');
|
|
919
|
+
|
|
920
|
+
const { data: comment } = await github.rest.issues.createComment({
|
|
921
|
+
owner: context.repo.owner,
|
|
922
|
+
repo: context.repo.repo,
|
|
923
|
+
issue_number: issueNumber,
|
|
924
|
+
body: body
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
core.setOutput('comment_id', comment.id);
|
|
928
|
+
core.setOutput('start_time', startTime);
|
|
929
|
+
|
|
930
|
+
- name: μ½λ 체ν¬μμ
|
|
931
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
932
|
+
uses: actions/checkout@v5
|
|
933
|
+
with:
|
|
934
|
+
ref: ${{ needs.get-branch-from-issue.outputs.branch_name }}
|
|
935
|
+
|
|
936
|
+
- name: JDK μ€μ
|
|
937
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
938
|
+
uses: actions/setup-java@v4
|
|
939
|
+
with:
|
|
940
|
+
distribution: 'temurin'
|
|
941
|
+
java-version: ${{ env.JAVA_VERSION }}
|
|
942
|
+
cache: 'gradle'
|
|
943
|
+
|
|
944
|
+
- name: Gradle κΆν μ€μ
|
|
945
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
946
|
+
run: chmod +x gradlew
|
|
947
|
+
|
|
948
|
+
# =================================================================
|
|
949
|
+
# β οΈ [μμ 2] νλ‘μ νΈλ³ Secret νμΌ μμ±
|
|
950
|
+
# =================================================================
|
|
951
|
+
- name: "[νμ] application-prod.yml μμ±"
|
|
952
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
953
|
+
run: |
|
|
954
|
+
mkdir -p $(dirname ${{ env.APPLICATION_YML_PATH }})
|
|
955
|
+
cat << 'EOF' > ${{ env.APPLICATION_YML_PATH }}
|
|
956
|
+
${{ secrets.APPLICATION_PROD_YML }}
|
|
957
|
+
EOF
|
|
958
|
+
# =================================================================
|
|
959
|
+
# β οΈ [μμ 2 λ] νλ‘μ νΈλ³ Secret νμΌ μμ± λ
|
|
960
|
+
# =================================================================
|
|
961
|
+
|
|
962
|
+
- name: Gradle λΉλ
|
|
963
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
964
|
+
run: ${{ env.GRADLE_BUILD_CMD }}
|
|
965
|
+
|
|
966
|
+
- name: μ§ν μν© - λΉλ μλ£
|
|
967
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
968
|
+
id: build_progress
|
|
969
|
+
uses: actions/github-script@v7
|
|
970
|
+
with:
|
|
971
|
+
script: |
|
|
972
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
973
|
+
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
974
|
+
const startTime = ${{ steps.progress.outputs.start_time }};
|
|
975
|
+
const now = Date.now();
|
|
976
|
+
const elapsed = now - startTime;
|
|
977
|
+
const minutes = Math.floor(elapsed / 60000);
|
|
978
|
+
const seconds = Math.floor((elapsed % 60000) / 1000);
|
|
979
|
+
const buildDuration = minutes > 0 ? `${minutes}λΆ ${seconds}μ΄` : `${seconds}μ΄`;
|
|
980
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
981
|
+
|
|
982
|
+
const body = [
|
|
983
|
+
'## π Issue Preview λΉλ μ€...',
|
|
984
|
+
'',
|
|
985
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
986
|
+
'',
|
|
987
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
988
|
+
'|------|------|----------|',
|
|
989
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
990
|
+
'| π³ Docker μ΄λ―Έμ§ μμ± | β³ μ§ν μ€... | - |',
|
|
991
|
+
'| π μλ² λ°°ν¬ & Health Check | βΈοΈ λκΈ° | - |',
|
|
992
|
+
'',
|
|
993
|
+
`**[π μ€μκ° λ‘κ·Έ 보기](${runUrl})**`,
|
|
994
|
+
'',
|
|
995
|
+
'---',
|
|
996
|
+
'*π€ μ΄ λκΈμ μλμΌλ‘ μ
λ°μ΄νΈλ©λλ€.*'
|
|
997
|
+
].join('\n');
|
|
998
|
+
|
|
999
|
+
await github.rest.issues.updateComment({
|
|
1000
|
+
owner: context.repo.owner,
|
|
1001
|
+
repo: context.repo.repo,
|
|
1002
|
+
comment_id: commentId,
|
|
1003
|
+
body: body
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
core.setOutput('build_duration', buildDuration);
|
|
1007
|
+
core.setOutput('docker_start', now);
|
|
1008
|
+
|
|
1009
|
+
- name: Docker λ‘κ·ΈμΈ
|
|
1010
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1011
|
+
uses: docker/login-action@v3
|
|
1012
|
+
with:
|
|
1013
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
1014
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
1015
|
+
|
|
1016
|
+
- name: Docker Buildx μ€μ
|
|
1017
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1018
|
+
uses: docker/setup-buildx-action@v3
|
|
1019
|
+
|
|
1020
|
+
- name: Docker μ΄λ―Έμ§ λΉλ & Push
|
|
1021
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1022
|
+
uses: docker/build-push-action@v5
|
|
1023
|
+
with:
|
|
1024
|
+
context: .
|
|
1025
|
+
file: ${{ env.DOCKERFILE_PATH }}
|
|
1026
|
+
push: true
|
|
1027
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:pr-${{ needs.get-branch-from-issue.outputs.issue_number }}
|
|
1028
|
+
cache-from: type=gha
|
|
1029
|
+
cache-to: type=gha,mode=max
|
|
1030
|
+
|
|
1031
|
+
- name: μ§ν μν© - Docker μλ£
|
|
1032
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1033
|
+
id: docker_progress
|
|
1034
|
+
uses: actions/github-script@v7
|
|
1035
|
+
with:
|
|
1036
|
+
script: |
|
|
1037
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1038
|
+
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1039
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
1040
|
+
const dockerStart = ${{ steps.build_progress.outputs.docker_start }};
|
|
1041
|
+
const now = Date.now();
|
|
1042
|
+
const dockerElapsed = now - dockerStart;
|
|
1043
|
+
const minutes = Math.floor(dockerElapsed / 60000);
|
|
1044
|
+
const seconds = Math.floor((dockerElapsed % 60000) / 1000);
|
|
1045
|
+
const dockerDuration = minutes > 0 ? `${minutes}λΆ ${seconds}μ΄` : `${seconds}μ΄`;
|
|
1046
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
1047
|
+
|
|
1048
|
+
const body = [
|
|
1049
|
+
'## π Issue Preview λΉλ μ€...',
|
|
1050
|
+
'',
|
|
1051
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1052
|
+
'',
|
|
1053
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1054
|
+
'|------|------|----------|',
|
|
1055
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
1056
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | β
μλ£ | ${dockerDuration} |`,
|
|
1057
|
+
'| π μλ² λ°°ν¬ & Health Check | β³ μ§ν μ€... | - |',
|
|
1058
|
+
'',
|
|
1059
|
+
`**[π μ€μκ° λ‘κ·Έ 보기](${runUrl})**`,
|
|
1060
|
+
'',
|
|
1061
|
+
'---',
|
|
1062
|
+
'*π€ μ΄ λκΈμ μλμΌλ‘ μ
λ°μ΄νΈλ©λλ€.*'
|
|
1063
|
+
].join('\n');
|
|
1064
|
+
|
|
1065
|
+
await github.rest.issues.updateComment({
|
|
1066
|
+
owner: context.repo.owner,
|
|
1067
|
+
repo: context.repo.repo,
|
|
1068
|
+
comment_id: commentId,
|
|
1069
|
+
body: body
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
core.setOutput('build_duration', buildDuration);
|
|
1073
|
+
core.setOutput('docker_duration', dockerDuration);
|
|
1074
|
+
core.setOutput('deploy_start', now);
|
|
1075
|
+
|
|
1076
|
+
- name: μλ²μ λ°°ν¬
|
|
1077
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1078
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
1079
|
+
with:
|
|
1080
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
1081
|
+
username: ${{ secrets.SERVER_USER }}
|
|
1082
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
1083
|
+
key: ${{ secrets.SSH_KEY }}
|
|
1084
|
+
port: ${{ env.SSH_PORT }}
|
|
1085
|
+
script: |
|
|
1086
|
+
set -e
|
|
1087
|
+
|
|
1088
|
+
# νκ²½ λ³μ μ€μ (λ°°ν¬ μλ²μ©)
|
|
1089
|
+
export PATH=$PATH:/usr/local/bin
|
|
1090
|
+
export PW="${{ secrets.SERVER_PASSWORD }}"
|
|
1091
|
+
|
|
1092
|
+
# π SSH μΈμ¦ λ°©μμ λ°λ₯Έ sudo μΆμν
|
|
1093
|
+
SSH_AUTH_METHOD="${{ env.SSH_AUTH_METHOD }}"
|
|
1094
|
+
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
1095
|
+
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
1096
|
+
SUDO() { sudo "$@"; }
|
|
1097
|
+
else
|
|
1098
|
+
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
1099
|
+
fi
|
|
1100
|
+
echo "π SSH μΈμ¦ λ°©μ: ${SSH_AUTH_METHOD}"
|
|
1101
|
+
|
|
1102
|
+
# λ³μ μ€μ
|
|
1103
|
+
ISSUE_NUMBER=${{ needs.get-branch-from-issue.outputs.issue_number }}
|
|
1104
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1105
|
+
CONTAINER_NAME="${PROJECT_NAME}-pr-${ISSUE_NUMBER}"
|
|
1106
|
+
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${ISSUE_NUMBER}"
|
|
1107
|
+
DOMAIN="${PROJECT_NAME}-pr-${ISSUE_NUMBER}.${{ env.PREVIEW_DOMAIN_SUFFIX }}"
|
|
1108
|
+
INTERNAL_PORT="${{ env.INTERNAL_PORT }}"
|
|
1109
|
+
TRAEFIK_NETWORK="${{ env.TRAEFIK_NETWORK }}"
|
|
1110
|
+
|
|
1111
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1112
|
+
echo "π Issue Preview λ°°ν¬ μμ"
|
|
1113
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1114
|
+
echo "π¦ νλ‘μ νΈ: ${PROJECT_NAME}"
|
|
1115
|
+
echo "π’ Issue λ²νΈ: #${ISSUE_NUMBER}"
|
|
1116
|
+
echo "π 컨ν
μ΄λ: ${CONTAINER_NAME}"
|
|
1117
|
+
echo "π λλ©μΈ: ${DOMAIN}"
|
|
1118
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1119
|
+
|
|
1120
|
+
# μ΄λ―Έμ§ Pull
|
|
1121
|
+
echo "π₯ Docker μ΄λ―Έμ§ Pull μ€..."
|
|
1122
|
+
SUDO docker pull "${IMAGE}"
|
|
1123
|
+
|
|
1124
|
+
# κΈ°μ‘΄ 컨ν
μ΄λ μμ
|
|
1125
|
+
echo "ποΈ κΈ°μ‘΄ 컨ν
μ΄λ μ 리 μ€..."
|
|
1126
|
+
SUDO docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
1127
|
+
|
|
1128
|
+
# λ³Όλ₯¨ λ§μ΄νΈ μ΅μ
ꡬμ±
|
|
1129
|
+
VOLUME_OPTS="-v /etc/localtime:/etc/localtime:ro"
|
|
1130
|
+
if [ -n "${{ env.PROJECT_TARGET_DIR }}" ] && [ -n "${{ env.PROJECT_MNT_DIR }}" ]; then
|
|
1131
|
+
VOLUME_OPTS="$VOLUME_OPTS -v ${{ env.PROJECT_TARGET_DIR }}:${{ env.PROJECT_MNT_DIR }}"
|
|
1132
|
+
fi
|
|
1133
|
+
|
|
1134
|
+
# μ 컨ν
μ΄λ μ€ν
|
|
1135
|
+
echo "π³ μ 컨ν
μ΄λ μ€ν μ€..."
|
|
1136
|
+
SUDO docker run -d \
|
|
1137
|
+
--name "${CONTAINER_NAME}" \
|
|
1138
|
+
--network "${TRAEFIK_NETWORK}" \
|
|
1139
|
+
--label "traefik.enable=true" \
|
|
1140
|
+
--label "traefik.http.routers.${CONTAINER_NAME}.rule=Host(\`${DOMAIN}\`)" \
|
|
1141
|
+
--label "traefik.http.routers.${CONTAINER_NAME}.entrypoints=web" \
|
|
1142
|
+
--label "traefik.http.services.${CONTAINER_NAME}.loadbalancer.server.port=${INTERNAL_PORT}" \
|
|
1143
|
+
-e TZ=Asia/Seoul \
|
|
1144
|
+
-e SPRING_PROFILES_ACTIVE=prod \
|
|
1145
|
+
$VOLUME_OPTS \
|
|
1146
|
+
"${IMAGE}"
|
|
1147
|
+
|
|
1148
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
1149
|
+
# Health Check (μ΅λ 120μ΄ λκΈ°) - νμ΄λΈλ¦¬λ λ°©μ
|
|
1150
|
+
# 1. HEALTH_CHECK_PATHκ° μ€μ λμ΄ μμΌλ©΄ HTTP μ²΄ν¬ μλ
|
|
1151
|
+
# 2. ν΄λ°±: λ‘κ·Έ ν¨ν΄ λ§€μΉ (HEALTH_CHECK_LOG_PATTERN)
|
|
1152
|
+
# ββββββββββββββββββββββββββββββββββββββββ
|
|
1153
|
+
echo ""
|
|
1154
|
+
echo "β³ Health Check μμ (μ΅λ 120μ΄ λκΈ°)..."
|
|
1155
|
+
HEALTH_PATH="${{ env.HEALTH_CHECK_PATH }}"
|
|
1156
|
+
LOG_PATTERN="${{ env.HEALTH_CHECK_LOG_PATTERN }}"
|
|
1157
|
+
MAX_RETRIES=24
|
|
1158
|
+
RETRY_COUNT=0
|
|
1159
|
+
HEALTH_CHECK_PASSED=false
|
|
1160
|
+
HEALTH_CHECK_METHOD=""
|
|
1161
|
+
|
|
1162
|
+
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
1163
|
+
sleep 5
|
|
1164
|
+
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
1165
|
+
|
|
1166
|
+
# 1. 컨ν
μ΄λ μν νμΈ
|
|
1167
|
+
STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${CONTAINER_NAME}" 2>/dev/null || echo "not_found")
|
|
1168
|
+
|
|
1169
|
+
if [ "$STATUS" = "exited" ]; then
|
|
1170
|
+
echo "β 컨ν
μ΄λ λΉμ μ μ’
λ£!"
|
|
1171
|
+
echo ""
|
|
1172
|
+
echo "π 컨ν
μ΄λ λ‘κ·Έ (μ΅κ·Ό 100μ€):"
|
|
1173
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1174
|
+
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
1175
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1176
|
+
exit 1
|
|
1177
|
+
fi
|
|
1178
|
+
|
|
1179
|
+
if [ "$STATUS" = "running" ]; then
|
|
1180
|
+
# 2. HTTP Health Check μλ (HEALTH_CHECK_PATHκ° μ€μ λ κ²½μ°μλ§)
|
|
1181
|
+
if [ -n "$HEALTH_PATH" ]; then
|
|
1182
|
+
HEALTH=$(SUDO docker exec "${CONTAINER_NAME}" wget -qO- "http://localhost:${INTERNAL_PORT}${HEALTH_PATH}" 2>/dev/null || echo "")
|
|
1183
|
+
|
|
1184
|
+
# Spring Actuator μλ΅ νμΈ ({"status":"UP"}) λλ μΌλ° HTTP μλ΅
|
|
1185
|
+
if echo "$HEALTH" | grep -q '"status":"UP"' || [ -n "$HEALTH" ]; then
|
|
1186
|
+
echo "β
μ μ κΈ°λ νμΈ! (HTTP μλ΅: ${HEALTH_PATH})"
|
|
1187
|
+
HEALTH_CHECK_PASSED=true
|
|
1188
|
+
HEALTH_CHECK_METHOD="HTTP"
|
|
1189
|
+
break
|
|
1190
|
+
fi
|
|
1191
|
+
fi
|
|
1192
|
+
|
|
1193
|
+
# 3. HTTP μλ΅ μμΌλ©΄ λ‘κ·Έ ν¨ν΄ λ§€μΉμΌλ‘ ν΄λ°±
|
|
1194
|
+
if [ -n "$LOG_PATTERN" ]; then
|
|
1195
|
+
STARTED=$(SUDO docker logs --tail 50 "${CONTAINER_NAME}" 2>&1 | grep -E "$LOG_PATTERN" || echo "")
|
|
1196
|
+
|
|
1197
|
+
if [ -n "$STARTED" ]; then
|
|
1198
|
+
echo "β
μ μ κΈ°λ νμΈ! (λ‘κ·Έ ν¨ν΄)"
|
|
1199
|
+
echo " $STARTED"
|
|
1200
|
+
HEALTH_CHECK_PASSED=true
|
|
1201
|
+
HEALTH_CHECK_METHOD="Log"
|
|
1202
|
+
break
|
|
1203
|
+
fi
|
|
1204
|
+
fi
|
|
1205
|
+
fi
|
|
1206
|
+
|
|
1207
|
+
echo "β³ λκΈ° μ€... ($RETRY_COUNT/$MAX_RETRIES) - μν: $STATUS"
|
|
1208
|
+
done
|
|
1209
|
+
|
|
1210
|
+
if [ "$HEALTH_CHECK_PASSED" = "false" ]; then
|
|
1211
|
+
echo ""
|
|
1212
|
+
echo "β Health Check νμμμ (120μ΄)"
|
|
1213
|
+
echo ""
|
|
1214
|
+
echo "π 컨ν
μ΄λ λ‘κ·Έ (μ΅κ·Ό 100μ€):"
|
|
1215
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1216
|
+
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
1217
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1218
|
+
exit 1
|
|
1219
|
+
fi
|
|
1220
|
+
|
|
1221
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1222
|
+
echo "β
λ°°ν¬ λ° Health Check μλ£! (λ°©μ: ${HEALTH_CHECK_METHOD})"
|
|
1223
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1224
|
+
|
|
1225
|
+
- name: λ°°ν¬ μλ£ μ½λ©νΈ
|
|
1226
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1227
|
+
uses: actions/github-script@v7
|
|
1228
|
+
with:
|
|
1229
|
+
script: |
|
|
1230
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1231
|
+
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1232
|
+
const buildDuration = '${{ steps.docker_progress.outputs.build_duration }}';
|
|
1233
|
+
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
1234
|
+
const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
|
|
1235
|
+
const now = Date.now();
|
|
1236
|
+
const deployElapsed = now - deployStart;
|
|
1237
|
+
const minutes = Math.floor(deployElapsed / 60000);
|
|
1238
|
+
const seconds = Math.floor((deployElapsed % 60000) / 1000);
|
|
1239
|
+
const deployDuration = minutes > 0 ? `${minutes}λΆ ${seconds}μ΄` : `${seconds}μ΄`;
|
|
1240
|
+
|
|
1241
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1242
|
+
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
1243
|
+
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
1244
|
+
const apiDocsPath = '${{ env.API_DOCS_PATH }}';
|
|
1245
|
+
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
1246
|
+
const domain = `${projectName}-pr-${issueNumber}.${domainSuffix}`;
|
|
1247
|
+
const previewUrl = `http://${domain}:${previewPort}`;
|
|
1248
|
+
const sha = '${{ steps.issue.outputs.sha }}';
|
|
1249
|
+
|
|
1250
|
+
// Preview νκ²½ ν
μ΄λΈ κ΅¬μ± (API_DOCS_PATHκ° μμ λλ§ API Docs ν μΆκ°)
|
|
1251
|
+
const envRows = [
|
|
1252
|
+
`| **Preview URL** | ${previewUrl} |`,
|
|
1253
|
+
];
|
|
1254
|
+
if (apiDocsPath) {
|
|
1255
|
+
envRows.push(`| **API Docs** | ${previewUrl}${apiDocsPath} |`);
|
|
1256
|
+
}
|
|
1257
|
+
envRows.push(`| **컨ν
μ΄λ** | \`${projectName}-pr-${issueNumber}\` |`);
|
|
1258
|
+
envRows.push(`| **λΈλμΉ** | \`${branchName}\` |`);
|
|
1259
|
+
envRows.push(`| **컀λ°** | \`${sha}\` |`);
|
|
1260
|
+
|
|
1261
|
+
const body = [
|
|
1262
|
+
'## β
Issue Preview λ°°ν¬ μλ£!',
|
|
1263
|
+
'',
|
|
1264
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1265
|
+
'',
|
|
1266
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1267
|
+
'|------|------|----------|',
|
|
1268
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
1269
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | β
μλ£ | ${dockerDuration} |`,
|
|
1270
|
+
`| π μλ² λ°°ν¬ & Health Check | β
μλ£ | ${deployDuration} |`,
|
|
1271
|
+
'',
|
|
1272
|
+
'### π Preview νκ²½',
|
|
1273
|
+
'| νλͺ© | κ° |',
|
|
1274
|
+
'|------|-----|',
|
|
1275
|
+
...envRows,
|
|
1276
|
+
'',
|
|
1277
|
+
'### π λͺ
λ Ήμ΄',
|
|
1278
|
+
'| λͺ
λ Ήμ΄ | μ€λͺ
|',
|
|
1279
|
+
'|--------|------|',
|
|
1280
|
+
'| `@suh-lab server build` | μ΅μ 컀λ°μΌλ‘ μ¬λ°°ν¬ |',
|
|
1281
|
+
'| `@suh-lab server destroy` | Preview νκ²½ μμ |',
|
|
1282
|
+
'| `@suh-lab server status` | νμ¬ μν νμΈ |',
|
|
1283
|
+
'',
|
|
1284
|
+
'<details>',
|
|
1285
|
+
'<summary>π§ κ³ κΈ λͺ
λ Ήμ΄ (λ€λ₯Έ Issue/PRμμ μ μ΄)</summary>',
|
|
1286
|
+
'',
|
|
1287
|
+
'```',
|
|
1288
|
+
`@suh-lab server build ${branchName}`,
|
|
1289
|
+
`@suh-lab server destroy ${branchName}`,
|
|
1290
|
+
`@suh-lab server status ${branchName}`,
|
|
1291
|
+
'```',
|
|
1292
|
+
'</details>',
|
|
1293
|
+
'',
|
|
1294
|
+
'---',
|
|
1295
|
+
'*π€ μ΄ λκΈμ Issue Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1296
|
+
].join('\n');
|
|
1297
|
+
|
|
1298
|
+
await github.rest.issues.updateComment({
|
|
1299
|
+
owner: context.repo.owner,
|
|
1300
|
+
repo: context.repo.repo,
|
|
1301
|
+
comment_id: commentId,
|
|
1302
|
+
body: body
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
- name: λΉλ/λ°°ν¬ μ€ν¨ μ μλ¬ μ½λ©νΈ
|
|
1306
|
+
if: failure() && steps.check_branch.outputs.exists == 'true'
|
|
1307
|
+
uses: actions/github-script@v7
|
|
1308
|
+
with:
|
|
1309
|
+
script: |
|
|
1310
|
+
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
1311
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1312
|
+
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
1313
|
+
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1314
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
1315
|
+
|
|
1316
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
1317
|
+
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
1318
|
+
|
|
1319
|
+
let buildStatus, dockerStatus, deployStatus;
|
|
1320
|
+
let buildTime = '-', dockerTime = '-';
|
|
1321
|
+
|
|
1322
|
+
if (!commentId) {
|
|
1323
|
+
buildStatus = 'β οΈ μ΄κΈ°ν μ€ν¨';
|
|
1324
|
+
dockerStatus = '-';
|
|
1325
|
+
deployStatus = '-';
|
|
1326
|
+
} else if (!buildDuration || buildDuration === '') {
|
|
1327
|
+
buildStatus = 'β μ€ν¨';
|
|
1328
|
+
dockerStatus = 'βΈοΈ λκΈ°';
|
|
1329
|
+
deployStatus = 'βΈοΈ λκΈ°';
|
|
1330
|
+
} else if (!dockerDuration || dockerDuration === '') {
|
|
1331
|
+
buildStatus = 'β
μλ£';
|
|
1332
|
+
dockerStatus = 'β μ€ν¨';
|
|
1333
|
+
deployStatus = 'βΈοΈ λκΈ°';
|
|
1334
|
+
buildTime = buildDuration;
|
|
1335
|
+
} else {
|
|
1336
|
+
buildStatus = 'β
μλ£';
|
|
1337
|
+
dockerStatus = 'β
μλ£';
|
|
1338
|
+
deployStatus = 'β μ€ν¨';
|
|
1339
|
+
buildTime = buildDuration;
|
|
1340
|
+
dockerTime = dockerDuration;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
const body = [
|
|
1344
|
+
'## β Issue Preview λ°°ν¬ μ€ν¨!',
|
|
1345
|
+
'',
|
|
1346
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1347
|
+
'',
|
|
1348
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1349
|
+
'|------|------|----------|',
|
|
1350
|
+
`| π¨ νλ‘μ νΈ λΉλ | ${buildStatus} | ${buildTime} |`,
|
|
1351
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | ${dockerStatus} | ${dockerTime} |`,
|
|
1352
|
+
`| π μλ² λ°°ν¬ & Health Check | ${deployStatus} | - |`,
|
|
1353
|
+
'',
|
|
1354
|
+
`**[π λΉλ/λ°°ν¬ λ‘κ·Έ νμΈ](${runUrl})**`,
|
|
1355
|
+
'',
|
|
1356
|
+
'### π κ°λ₯ν μμΈ',
|
|
1357
|
+
'- Gradle λΉλ μ€ν¨',
|
|
1358
|
+
'- Docker μ΄λ―Έμ§ λΉλ μ€ν¨',
|
|
1359
|
+
'- 컨ν
μ΄λ μμ μ€ν¨ (Spring Boot κΈ°λ μ€λ₯)',
|
|
1360
|
+
'- Health Check νμμμ (120μ΄ λ΄ κΈ°λ μλ£ μλ¨)',
|
|
1361
|
+
'',
|
|
1362
|
+
'### π‘ λ€μ λ¨κ³',
|
|
1363
|
+
'1. μ λ§ν¬μμ λΉλ/λ°°ν¬ λ‘κ·Έλ₯Ό νμΈνμΈμ',
|
|
1364
|
+
'2. λ¬Έμ λ₯Ό μμ ν ν λ€μ μλνμΈμ: `@suh-lab server build`',
|
|
1365
|
+
'',
|
|
1366
|
+
'---',
|
|
1367
|
+
'*π€ μ΄ λκΈμ Issue Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1368
|
+
].join('\n');
|
|
1369
|
+
|
|
1370
|
+
if (commentId) {
|
|
1371
|
+
await github.rest.issues.updateComment({
|
|
1372
|
+
owner: context.repo.owner,
|
|
1373
|
+
repo: context.repo.repo,
|
|
1374
|
+
comment_id: commentId,
|
|
1375
|
+
body: body
|
|
1376
|
+
});
|
|
1377
|
+
} else {
|
|
1378
|
+
await github.rest.issues.createComment({
|
|
1379
|
+
owner: context.repo.owner,
|
|
1380
|
+
repo: context.repo.repo,
|
|
1381
|
+
issue_number: issueNumber,
|
|
1382
|
+
body: body
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
# -----------------------------------------------------------------
|
|
1387
|
+
# Job 3: Preview μμ (PR/Issue λ«ν λλ destroy λͺ
λ Ή)
|
|
1388
|
+
# -----------------------------------------------------------------
|
|
1389
|
+
destroy-preview:
|
|
1390
|
+
name: Preview μμ
|
|
1391
|
+
# needs μμ‘΄μ± μ κ±°: PR/Issue λ«ν μ΄λ²€νΈμμλ λ
립μ μΌλ‘ μ€νλλλ‘ ν¨
|
|
1392
|
+
if: |
|
|
1393
|
+
(github.event_name == 'pull_request' && github.event.action == 'closed') ||
|
|
1394
|
+
(github.event_name == 'issues' && github.event.action == 'closed') ||
|
|
1395
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@suh-lab') && contains(github.event.comment.body, 'server destroy'))
|
|
1396
|
+
runs-on: ubuntu-latest
|
|
1397
|
+
steps:
|
|
1398
|
+
- name: PR/Issue λ²νΈ κ°μ Έμ€κΈ°
|
|
1399
|
+
id: pr_number
|
|
1400
|
+
run: |
|
|
1401
|
+
if [[ "${{ github.event_name }}" == "issue_comment" ]]; then
|
|
1402
|
+
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
|
|
1403
|
+
echo "type=comment" >> $GITHUB_OUTPUT
|
|
1404
|
+
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
|
1405
|
+
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
|
1406
|
+
echo "type=pr_closed" >> $GITHUB_OUTPUT
|
|
1407
|
+
else
|
|
1408
|
+
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
|
|
1409
|
+
echo "type=issue_closed" >> $GITHUB_OUTPUT
|
|
1410
|
+
fi
|
|
1411
|
+
|
|
1412
|
+
- name: 컨ν
μ΄λ & μ΄λ―Έμ§ μμ
|
|
1413
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
1414
|
+
with:
|
|
1415
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
1416
|
+
username: ${{ secrets.SERVER_USER }}
|
|
1417
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
1418
|
+
key: ${{ secrets.SSH_KEY }}
|
|
1419
|
+
port: ${{ env.SSH_PORT }}
|
|
1420
|
+
script: |
|
|
1421
|
+
# νκ²½ λ³μ μ€μ (λ°°ν¬ μλ²μ©)
|
|
1422
|
+
export PATH=$PATH:/usr/local/bin
|
|
1423
|
+
export PW="${{ secrets.SERVER_PASSWORD }}"
|
|
1424
|
+
|
|
1425
|
+
# π SSH μΈμ¦ λ°©μμ λ°λ₯Έ sudo μΆμν
|
|
1426
|
+
SSH_AUTH_METHOD="${{ env.SSH_AUTH_METHOD }}"
|
|
1427
|
+
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
1428
|
+
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
1429
|
+
SUDO() { sudo "$@"; }
|
|
1430
|
+
else
|
|
1431
|
+
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
1432
|
+
fi
|
|
1433
|
+
echo "π SSH μΈμ¦ λ°©μ: ${SSH_AUTH_METHOD}"
|
|
1434
|
+
|
|
1435
|
+
PR_NUMBER=${{ steps.pr_number.outputs.number }}
|
|
1436
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1437
|
+
CONTAINER_NAME="${PROJECT_NAME}-pr-${PR_NUMBER}"
|
|
1438
|
+
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
|
|
1439
|
+
|
|
1440
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1441
|
+
echo "ποΈ Preview μμ "
|
|
1442
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1443
|
+
echo "π¦ νλ‘μ νΈ: ${PROJECT_NAME}"
|
|
1444
|
+
echo "π’ λ²νΈ: #${PR_NUMBER}"
|
|
1445
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1446
|
+
|
|
1447
|
+
# 컨ν
μ΄λ μμ
|
|
1448
|
+
echo "π³ 컨ν
μ΄λ μμ μ€..."
|
|
1449
|
+
SUDO docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
1450
|
+
|
|
1451
|
+
# μ΄λ―Έμ§ μμ
|
|
1452
|
+
echo "πΌοΈ μ΄λ―Έμ§ μμ μ€..."
|
|
1453
|
+
SUDO docker rmi "${IMAGE}" 2>/dev/null || true
|
|
1454
|
+
|
|
1455
|
+
echo "β
μμ μλ£!"
|
|
1456
|
+
|
|
1457
|
+
- name: μμ μλ£ μ½λ©νΈ
|
|
1458
|
+
if: |
|
|
1459
|
+
github.event_name == 'issue_comment' ||
|
|
1460
|
+
(github.event_name == 'pull_request' && github.event.action == 'closed') ||
|
|
1461
|
+
(github.event_name == 'issues' && github.event.action == 'closed')
|
|
1462
|
+
uses: actions/github-script@v7
|
|
1463
|
+
with:
|
|
1464
|
+
script: |
|
|
1465
|
+
const number = ${{ steps.pr_number.outputs.number }};
|
|
1466
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1467
|
+
|
|
1468
|
+
const body = [
|
|
1469
|
+
'## ποΈ Preview νκ²½ μμ μλ£!',
|
|
1470
|
+
'',
|
|
1471
|
+
'| νλͺ© | κ° |',
|
|
1472
|
+
'|------|-----|',
|
|
1473
|
+
`| **컨ν
μ΄λ** | \`${projectName}-pr-${number}\` |`,
|
|
1474
|
+
'| **μν** | μμ λ¨ |',
|
|
1475
|
+
'',
|
|
1476
|
+
'λ€μ λ°°ν¬νλ €λ©΄: `@suh-lab server build`',
|
|
1477
|
+
'',
|
|
1478
|
+
'---',
|
|
1479
|
+
'*π€ μ΄ λκΈμ PR/Issue Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1480
|
+
].join('\n');
|
|
1481
|
+
|
|
1482
|
+
await github.rest.issues.createComment({
|
|
1483
|
+
owner: context.repo.owner,
|
|
1484
|
+
repo: context.repo.repo,
|
|
1485
|
+
issue_number: number,
|
|
1486
|
+
body: body
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
# -----------------------------------------------------------------
|
|
1490
|
+
# Job 4: μν νμΈ (PR/Issue λͺ¨λ μ§μ)
|
|
1491
|
+
# -----------------------------------------------------------------
|
|
1492
|
+
check-status:
|
|
1493
|
+
name: Preview μν νμΈ
|
|
1494
|
+
needs: check-command
|
|
1495
|
+
if: needs.check-command.outputs.is_valid == 'true' && needs.check-command.outputs.command == 'status'
|
|
1496
|
+
runs-on: ubuntu-latest
|
|
1497
|
+
steps:
|
|
1498
|
+
- name: 컨ν
μ΄λ μν νμΈ
|
|
1499
|
+
id: status
|
|
1500
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
1501
|
+
with:
|
|
1502
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
1503
|
+
username: ${{ secrets.SERVER_USER }}
|
|
1504
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
1505
|
+
key: ${{ secrets.SSH_KEY }}
|
|
1506
|
+
port: ${{ env.SSH_PORT }}
|
|
1507
|
+
script: |
|
|
1508
|
+
# νκ²½ λ³μ μ€μ (λ°°ν¬ μλ²μ©)
|
|
1509
|
+
export PATH=$PATH:/usr/local/bin
|
|
1510
|
+
|
|
1511
|
+
NUMBER=${{ github.event.issue.number }}
|
|
1512
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1513
|
+
CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
|
|
1514
|
+
|
|
1515
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1516
|
+
echo "π Preview μν νμΈ"
|
|
1517
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1518
|
+
|
|
1519
|
+
if docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1520
|
+
echo "STATUS=running"
|
|
1521
|
+
echo "β
컨ν
μ΄λ μ€ν μ€"
|
|
1522
|
+
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.RunningFor}}"
|
|
1523
|
+
else
|
|
1524
|
+
echo "STATUS=not_found"
|
|
1525
|
+
echo "β 컨ν
μ΄λ μμ"
|
|
1526
|
+
fi
|
|
1527
|
+
|
|
1528
|
+
- name: μν μ½λ©νΈ (μ€ν μ€)
|
|
1529
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
1530
|
+
id: check_running
|
|
1531
|
+
continue-on-error: true
|
|
1532
|
+
with:
|
|
1533
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
1534
|
+
username: ${{ secrets.SERVER_USER }}
|
|
1535
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
1536
|
+
key: ${{ secrets.SSH_KEY }}
|
|
1537
|
+
port: ${{ env.SSH_PORT }}
|
|
1538
|
+
script: |
|
|
1539
|
+
# νκ²½ λ³μ μ€μ (λ°°ν¬ μλ²μ©)
|
|
1540
|
+
export PATH=$PATH:/usr/local/bin
|
|
1541
|
+
|
|
1542
|
+
NUMBER=${{ github.event.issue.number }}
|
|
1543
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1544
|
+
CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
|
|
1545
|
+
docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"
|
|
1546
|
+
|
|
1547
|
+
- name: μν μ½λ©νΈ μμ±
|
|
1548
|
+
uses: actions/github-script@v7
|
|
1549
|
+
with:
|
|
1550
|
+
script: |
|
|
1551
|
+
const number = context.issue.number;
|
|
1552
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1553
|
+
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
1554
|
+
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
1555
|
+
const domain = `${projectName}-pr-${number}.${domainSuffix}`;
|
|
1556
|
+
const previewUrl = `http://${domain}:${previewPort}`;
|
|
1557
|
+
const isRunning = '${{ steps.check_running.outcome }}' === 'success';
|
|
1558
|
+
const isPr = '${{ needs.check-command.outputs.is_pr }}' === 'true';
|
|
1559
|
+
const contextType = isPr ? 'PR' : 'Issue';
|
|
1560
|
+
|
|
1561
|
+
let body;
|
|
1562
|
+
if (isRunning) {
|
|
1563
|
+
body = [
|
|
1564
|
+
'## β
Preview νκ²½ μ€ν μ€',
|
|
1565
|
+
'',
|
|
1566
|
+
'| νλͺ© | κ° |',
|
|
1567
|
+
'|------|-----|',
|
|
1568
|
+
`| **Preview URL** | ${previewUrl} |`,
|
|
1569
|
+
`| **컨ν
μ΄λ** | \`${projectName}-pr-${number}\` |`,
|
|
1570
|
+
'| **μν** | π’ Running |',
|
|
1571
|
+
'',
|
|
1572
|
+
'### π λͺ
λ Ήμ΄',
|
|
1573
|
+
'| λͺ
λ Ήμ΄ | μ€λͺ
|',
|
|
1574
|
+
'|--------|------|',
|
|
1575
|
+
'| `@suh-lab server build` | μ΅μ 컀λ°μΌλ‘ μ¬λ°°ν¬ |',
|
|
1576
|
+
'| `@suh-lab server destroy` | Preview νκ²½ μμ |',
|
|
1577
|
+
'',
|
|
1578
|
+
'---',
|
|
1579
|
+
`*π€ μ΄ λκΈμ ${contextType} Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*`
|
|
1580
|
+
].join('\n');
|
|
1581
|
+
} else {
|
|
1582
|
+
body = [
|
|
1583
|
+
'## β Preview νκ²½ μμ',
|
|
1584
|
+
'',
|
|
1585
|
+
'| νλͺ© | κ° |',
|
|
1586
|
+
'|------|-----|',
|
|
1587
|
+
`| **컨ν
μ΄λ** | \`${projectName}-pr-${number}\` |`,
|
|
1588
|
+
'| **μν** | π΄ Not Found |',
|
|
1589
|
+
'',
|
|
1590
|
+
'λ°°ν¬νλ €λ©΄: `@suh-lab server build`',
|
|
1591
|
+
'',
|
|
1592
|
+
'---',
|
|
1593
|
+
`*π€ μ΄ λκΈμ ${contextType} Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*`
|
|
1594
|
+
].join('\n');
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
await github.rest.issues.createComment({
|
|
1598
|
+
owner: context.repo.owner,
|
|
1599
|
+
repo: context.repo.repo,
|
|
1600
|
+
issue_number: number,
|
|
1601
|
+
body: body
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
# ============================================================
|
|
1605
|
+
# Custom Branch Preview Jobs
|
|
1606
|
+
# νΉμ λΈλμΉλ₯Ό μ§μ νμ¬ λΉλ/μμ /μν νμΈνλ κΈ°λ₯
|
|
1607
|
+
# λͺ
λ Ήμ΄: @suh-lab server build <λΈλμΉλͺ
>
|
|
1608
|
+
# ============================================================
|
|
1609
|
+
|
|
1610
|
+
build-preview-custom-branch:
|
|
1611
|
+
name: Preview λΉλ & λ°°ν¬ (Custom Branch)
|
|
1612
|
+
needs: check-command
|
|
1613
|
+
if: |
|
|
1614
|
+
needs.check-command.outputs.is_valid == 'true' &&
|
|
1615
|
+
needs.check-command.outputs.command == 'build' &&
|
|
1616
|
+
needs.check-command.outputs.is_custom_branch == 'true'
|
|
1617
|
+
runs-on: ubuntu-latest
|
|
1618
|
+
steps:
|
|
1619
|
+
- name: λΈλμΉλͺ
μμ Issue λ²νΈ μΆμΆ
|
|
1620
|
+
id: extract
|
|
1621
|
+
uses: actions/github-script@v7
|
|
1622
|
+
with:
|
|
1623
|
+
script: |
|
|
1624
|
+
const branchName = '${{ needs.check-command.outputs.custom_branch }}';
|
|
1625
|
+
const issueMatch = branchName.match(/#(\d+)/);
|
|
1626
|
+
|
|
1627
|
+
if (issueMatch) {
|
|
1628
|
+
core.setOutput('issue_number', issueMatch[1]);
|
|
1629
|
+
core.setOutput('use_hash', 'false');
|
|
1630
|
+
core.setOutput('container_suffix', `pr-${issueMatch[1]}`);
|
|
1631
|
+
} else {
|
|
1632
|
+
// ν΄μ μμ±
|
|
1633
|
+
let hash = 0;
|
|
1634
|
+
for (let i = 0; i < branchName.length; i++) {
|
|
1635
|
+
const char = branchName.charCodeAt(i);
|
|
1636
|
+
hash = ((hash << 5) - hash) + char;
|
|
1637
|
+
hash = hash & hash;
|
|
1638
|
+
}
|
|
1639
|
+
const hashStr = Math.abs(hash).toString(16).slice(-6).padStart(6, '0');
|
|
1640
|
+
core.setOutput('container_suffix', `custom-${hashStr}`);
|
|
1641
|
+
core.setOutput('use_hash', 'true');
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
core.setOutput('branch_name', branchName);
|
|
1645
|
+
|
|
1646
|
+
- name: λΈλμΉ μ‘΄μ¬ μ¬λΆ νμΈ
|
|
1647
|
+
id: check_branch
|
|
1648
|
+
uses: actions/github-script@v7
|
|
1649
|
+
with:
|
|
1650
|
+
script: |
|
|
1651
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1652
|
+
try {
|
|
1653
|
+
await github.rest.repos.getBranch({
|
|
1654
|
+
owner: context.repo.owner,
|
|
1655
|
+
repo: context.repo.repo,
|
|
1656
|
+
branch: branchName
|
|
1657
|
+
});
|
|
1658
|
+
core.setOutput('exists', 'true');
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
if (error.status === 404) {
|
|
1661
|
+
core.setOutput('exists', 'false');
|
|
1662
|
+
} else {
|
|
1663
|
+
throw error;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
- name: λΈλμΉ μμ μλ¬ μ½λ©νΈ
|
|
1668
|
+
if: steps.check_branch.outputs.exists == 'false'
|
|
1669
|
+
uses: actions/github-script@v7
|
|
1670
|
+
with:
|
|
1671
|
+
script: |
|
|
1672
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1673
|
+
|
|
1674
|
+
const body = [
|
|
1675
|
+
'## β λΈλμΉλ₯Ό μ°Ύμ μ μμ΅λλ€',
|
|
1676
|
+
'',
|
|
1677
|
+
'| νλͺ© | κ° |',
|
|
1678
|
+
'|------|-----|',
|
|
1679
|
+
`| **λΈλμΉ** | \`${branchName}\` |`,
|
|
1680
|
+
'',
|
|
1681
|
+
'### π‘ νμΈ μ¬ν',
|
|
1682
|
+
'1. λΈλμΉλͺ
μ΄ μ ννμ§ νμΈνμΈμ',
|
|
1683
|
+
'2. λΈλμΉκ° pushλμλμ§ νμΈνμΈμ',
|
|
1684
|
+
'3. λΈλμΉκ° μμ λμ§ μμλμ§ νμΈνμΈμ',
|
|
1685
|
+
'',
|
|
1686
|
+
'---',
|
|
1687
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1688
|
+
].join('\n');
|
|
1689
|
+
|
|
1690
|
+
await github.rest.issues.createComment({
|
|
1691
|
+
owner: context.repo.owner,
|
|
1692
|
+
repo: context.repo.repo,
|
|
1693
|
+
issue_number: context.issue.number,
|
|
1694
|
+
body: body
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
core.setFailed('λΈλμΉλ₯Ό μ°Ύμ μ μμ΅λλ€: ' + branchName);
|
|
1698
|
+
|
|
1699
|
+
- name: 체ν¬μμ
|
|
1700
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1701
|
+
uses: actions/checkout@v5
|
|
1702
|
+
with:
|
|
1703
|
+
ref: ${{ steps.extract.outputs.branch_name }}
|
|
1704
|
+
|
|
1705
|
+
- name: μ΅μ μ»€λ° μ 보 κ°μ Έμ€κΈ°
|
|
1706
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1707
|
+
id: commit
|
|
1708
|
+
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
1709
|
+
|
|
1710
|
+
- name: μ§ν μν© μ½λ©νΈ μμ±
|
|
1711
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1712
|
+
id: progress
|
|
1713
|
+
uses: actions/github-script@v7
|
|
1714
|
+
with:
|
|
1715
|
+
script: |
|
|
1716
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1717
|
+
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
1718
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1719
|
+
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1720
|
+
|
|
1721
|
+
const body = [
|
|
1722
|
+
'## π Custom Branch Preview λ°°ν¬ μμ',
|
|
1723
|
+
'',
|
|
1724
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1725
|
+
`**컀λ°**: \`${sha}\``,
|
|
1726
|
+
'',
|
|
1727
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1728
|
+
'|------|------|----------|',
|
|
1729
|
+
'| π¨ νλ‘μ νΈ λΉλ | β³ μ§ν μ€... | - |',
|
|
1730
|
+
'| π³ Docker μ΄λ―Έμ§ μμ± | βΈοΈ λκΈ° | - |',
|
|
1731
|
+
'| π μλ² λ°°ν¬ & Health Check | βΈοΈ λκΈ° | - |',
|
|
1732
|
+
'',
|
|
1733
|
+
'---',
|
|
1734
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1735
|
+
].join('\n');
|
|
1736
|
+
|
|
1737
|
+
const comment = await github.rest.issues.createComment({
|
|
1738
|
+
owner: context.repo.owner,
|
|
1739
|
+
repo: context.repo.repo,
|
|
1740
|
+
issue_number: context.issue.number,
|
|
1741
|
+
body: body
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
core.setOutput('comment_id', comment.data.id);
|
|
1745
|
+
|
|
1746
|
+
- name: JDK μ€μ
|
|
1747
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1748
|
+
uses: actions/setup-java@v4
|
|
1749
|
+
with:
|
|
1750
|
+
java-version: ${{ env.JAVA_VERSION }}
|
|
1751
|
+
distribution: 'temurin'
|
|
1752
|
+
|
|
1753
|
+
- name: Gradle μΊμ
|
|
1754
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1755
|
+
uses: actions/cache@v4
|
|
1756
|
+
with:
|
|
1757
|
+
path: |
|
|
1758
|
+
~/.gradle/caches
|
|
1759
|
+
~/.gradle/wrapper
|
|
1760
|
+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
1761
|
+
restore-keys: |
|
|
1762
|
+
${{ runner.os }}-gradle-
|
|
1763
|
+
|
|
1764
|
+
- name: Gradle λΉλ
|
|
1765
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1766
|
+
id: build
|
|
1767
|
+
run: |
|
|
1768
|
+
BUILD_START=$(date +%s)
|
|
1769
|
+
chmod +x ./gradlew
|
|
1770
|
+
./gradlew clean bootJar -x test --no-daemon
|
|
1771
|
+
BUILD_END=$(date +%s)
|
|
1772
|
+
BUILD_DURATION=$((BUILD_END - BUILD_START))
|
|
1773
|
+
echo "duration=${BUILD_DURATION}s" >> $GITHUB_OUTPUT
|
|
1774
|
+
|
|
1775
|
+
- name: λΉλ μλ£ μ§ν μν© μ
λ°μ΄νΈ
|
|
1776
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1777
|
+
id: build_progress
|
|
1778
|
+
uses: actions/github-script@v7
|
|
1779
|
+
with:
|
|
1780
|
+
script: |
|
|
1781
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1782
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1783
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1784
|
+
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1785
|
+
const buildDuration = '${{ steps.build.outputs.duration }}';
|
|
1786
|
+
|
|
1787
|
+
const body = [
|
|
1788
|
+
'## π Custom Branch Preview λ°°ν¬ μμ',
|
|
1789
|
+
'',
|
|
1790
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1791
|
+
`**컀λ°**: \`${sha}\``,
|
|
1792
|
+
'',
|
|
1793
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1794
|
+
'|------|------|----------|',
|
|
1795
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
1796
|
+
'| π³ Docker μ΄λ―Έμ§ μμ± | β³ μ§ν μ€... | - |',
|
|
1797
|
+
'| π μλ² λ°°ν¬ & Health Check | βΈοΈ λκΈ° | - |',
|
|
1798
|
+
'',
|
|
1799
|
+
'---',
|
|
1800
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1801
|
+
].join('\n');
|
|
1802
|
+
|
|
1803
|
+
await github.rest.issues.updateComment({
|
|
1804
|
+
owner: context.repo.owner,
|
|
1805
|
+
repo: context.repo.repo,
|
|
1806
|
+
comment_id: commentId,
|
|
1807
|
+
body: body
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
core.setOutput('build_duration', buildDuration);
|
|
1811
|
+
|
|
1812
|
+
- name: Docker Hub λ‘κ·ΈμΈ
|
|
1813
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1814
|
+
uses: docker/login-action@v3
|
|
1815
|
+
with:
|
|
1816
|
+
username: ${{ secrets.DOCKER_USERNAME }}
|
|
1817
|
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
1818
|
+
|
|
1819
|
+
- name: Docker μ΄λ―Έμ§ λΉλ & νΈμ
|
|
1820
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1821
|
+
id: docker
|
|
1822
|
+
run: |
|
|
1823
|
+
DOCKER_START=$(date +%s)
|
|
1824
|
+
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
1825
|
+
IMAGE_TAG="${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${CONTAINER_SUFFIX}"
|
|
1826
|
+
docker build -t $IMAGE_TAG .
|
|
1827
|
+
docker push $IMAGE_TAG
|
|
1828
|
+
DOCKER_END=$(date +%s)
|
|
1829
|
+
DOCKER_DURATION=$((DOCKER_END - DOCKER_START))
|
|
1830
|
+
echo "duration=${DOCKER_DURATION}s" >> $GITHUB_OUTPUT
|
|
1831
|
+
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
|
|
1832
|
+
|
|
1833
|
+
- name: Docker μλ£ μ§ν μν© μ
λ°μ΄νΈ
|
|
1834
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1835
|
+
id: docker_progress
|
|
1836
|
+
uses: actions/github-script@v7
|
|
1837
|
+
with:
|
|
1838
|
+
script: |
|
|
1839
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1840
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1841
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1842
|
+
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1843
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
1844
|
+
const dockerDuration = '${{ steps.docker.outputs.duration }}';
|
|
1845
|
+
|
|
1846
|
+
const body = [
|
|
1847
|
+
'## π Custom Branch Preview λ°°ν¬ μμ',
|
|
1848
|
+
'',
|
|
1849
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1850
|
+
`**컀λ°**: \`${sha}\``,
|
|
1851
|
+
'',
|
|
1852
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1853
|
+
'|------|------|----------|',
|
|
1854
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
1855
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | β
μλ£ | ${dockerDuration} |`,
|
|
1856
|
+
'| π μλ² λ°°ν¬ & Health Check | β³ μ§ν μ€... | - |',
|
|
1857
|
+
'',
|
|
1858
|
+
'---',
|
|
1859
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1860
|
+
].join('\n');
|
|
1861
|
+
|
|
1862
|
+
await github.rest.issues.updateComment({
|
|
1863
|
+
owner: context.repo.owner,
|
|
1864
|
+
repo: context.repo.repo,
|
|
1865
|
+
comment_id: commentId,
|
|
1866
|
+
body: body
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
core.setOutput('docker_duration', dockerDuration);
|
|
1870
|
+
|
|
1871
|
+
- name: μλ² λ°°ν¬
|
|
1872
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1873
|
+
id: deploy
|
|
1874
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
1875
|
+
with:
|
|
1876
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
1877
|
+
username: ${{ secrets.SERVER_USER }}
|
|
1878
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
1879
|
+
key: ${{ secrets.SSH_KEY }}
|
|
1880
|
+
port: ${{ env.SSH_PORT }}
|
|
1881
|
+
script: |
|
|
1882
|
+
export PATH=$PATH:/usr/local/bin
|
|
1883
|
+
|
|
1884
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
1885
|
+
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
1886
|
+
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
1887
|
+
IMAGE_TAG="${{ steps.docker.outputs.image_tag }}"
|
|
1888
|
+
PREVIEW_PORT="${{ env.PREVIEW_PORT }}"
|
|
1889
|
+
DOMAIN_SUFFIX="${{ env.PREVIEW_DOMAIN_SUFFIX }}"
|
|
1890
|
+
|
|
1891
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1892
|
+
echo "π Custom Branch Preview λ°°ν¬"
|
|
1893
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1894
|
+
|
|
1895
|
+
docker pull $IMAGE_TAG
|
|
1896
|
+
|
|
1897
|
+
if docker ps -a --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1898
|
+
echo "ποΈ κΈ°μ‘΄ 컨ν
μ΄λ μμ : ${CONTAINER_NAME}"
|
|
1899
|
+
docker stop ${CONTAINER_NAME} || true
|
|
1900
|
+
docker rm ${CONTAINER_NAME} || true
|
|
1901
|
+
fi
|
|
1902
|
+
|
|
1903
|
+
echo "π³ μ 컨ν
μ΄λ μμ: ${CONTAINER_NAME}"
|
|
1904
|
+
docker run -d \
|
|
1905
|
+
--name ${CONTAINER_NAME} \
|
|
1906
|
+
--network traefik-network \
|
|
1907
|
+
-l "traefik.enable=true" \
|
|
1908
|
+
-l "traefik.http.routers.${CONTAINER_NAME}.rule=Host(\`${CONTAINER_NAME}.${DOMAIN_SUFFIX}\`)" \
|
|
1909
|
+
-l "traefik.http.routers.${CONTAINER_NAME}.entrypoints=web" \
|
|
1910
|
+
-l "traefik.http.services.${CONTAINER_NAME}.loadbalancer.server.port=${PREVIEW_PORT}" \
|
|
1911
|
+
${IMAGE_TAG}
|
|
1912
|
+
|
|
1913
|
+
echo "β³ Health Check λκΈ° (μ΅λ 60μ΄)..."
|
|
1914
|
+
for i in {1..12}; do
|
|
1915
|
+
if docker ps --filter "name=${CONTAINER_NAME}" --filter "status=running" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1916
|
+
echo "β
컨ν
μ΄λ μ€ν νμΈλ¨"
|
|
1917
|
+
break
|
|
1918
|
+
fi
|
|
1919
|
+
echo "λκΈ° μ€... ($i/12)"
|
|
1920
|
+
sleep 5
|
|
1921
|
+
done
|
|
1922
|
+
|
|
1923
|
+
if ! docker ps --filter "name=${CONTAINER_NAME}" --filter "status=running" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1924
|
+
echo "β λ°°ν¬ μ€ν¨: 컨ν
μ΄λκ° μ€νλμ§ μμ"
|
|
1925
|
+
exit 1
|
|
1926
|
+
fi
|
|
1927
|
+
|
|
1928
|
+
echo "β
λ°°ν¬ μλ£!"
|
|
1929
|
+
echo "π Preview URL: http://${CONTAINER_NAME}.${DOMAIN_SUFFIX}:${PREVIEW_PORT}"
|
|
1930
|
+
|
|
1931
|
+
- name: λ°°ν¬ μλ£ μκ° κΈ°λ‘
|
|
1932
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1933
|
+
id: deploy_time
|
|
1934
|
+
run: echo "duration=μλ£" >> $GITHUB_OUTPUT
|
|
1935
|
+
|
|
1936
|
+
- name: λ°°ν¬ μλ£ μ½λ©νΈ
|
|
1937
|
+
if: steps.check_branch.outputs.exists == 'true'
|
|
1938
|
+
uses: actions/github-script@v7
|
|
1939
|
+
with:
|
|
1940
|
+
script: |
|
|
1941
|
+
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1942
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
1943
|
+
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
1944
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1945
|
+
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1946
|
+
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
1947
|
+
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
1948
|
+
const apiDocsPath = '${{ env.API_DOCS_PATH }}';
|
|
1949
|
+
|
|
1950
|
+
const containerName = `${projectName}-${containerSuffix}`;
|
|
1951
|
+
const previewUrl = `http://${containerName}.${domainSuffix}:${previewPort}`;
|
|
1952
|
+
|
|
1953
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
1954
|
+
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
1955
|
+
const deployDuration = '${{ steps.deploy_time.outputs.duration }}';
|
|
1956
|
+
|
|
1957
|
+
const envRows = [
|
|
1958
|
+
`| **Preview URL** | ${previewUrl} |`,
|
|
1959
|
+
];
|
|
1960
|
+
if (apiDocsPath) {
|
|
1961
|
+
envRows.push(`| **API Docs** | ${previewUrl}${apiDocsPath} |`);
|
|
1962
|
+
}
|
|
1963
|
+
envRows.push(`| **컨ν
μ΄λ** | \`${containerName}\` |`);
|
|
1964
|
+
envRows.push(`| **λΈλμΉ** | \`${branchName}\` |`);
|
|
1965
|
+
envRows.push(`| **컀λ°** | \`${sha}\` |`);
|
|
1966
|
+
|
|
1967
|
+
const body = [
|
|
1968
|
+
'## β
Custom Branch Preview λ°°ν¬ μλ£!',
|
|
1969
|
+
'',
|
|
1970
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
1971
|
+
'',
|
|
1972
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
1973
|
+
'|------|------|----------|',
|
|
1974
|
+
`| π¨ νλ‘μ νΈ λΉλ | β
μλ£ | ${buildDuration} |`,
|
|
1975
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | β
μλ£ | ${dockerDuration} |`,
|
|
1976
|
+
`| π μλ² λ°°ν¬ & Health Check | β
μλ£ | ${deployDuration} |`,
|
|
1977
|
+
'',
|
|
1978
|
+
'### π Preview νκ²½',
|
|
1979
|
+
'| νλͺ© | κ° |',
|
|
1980
|
+
'|------|-----|',
|
|
1981
|
+
...envRows,
|
|
1982
|
+
'',
|
|
1983
|
+
'### π λͺ
λ Ήμ΄',
|
|
1984
|
+
'| λͺ
λ Ήμ΄ | μ€λͺ
|',
|
|
1985
|
+
'|--------|------|',
|
|
1986
|
+
`| \`@suh-lab server build ${branchName}\` | μ΅μ 컀λ°μΌλ‘ μ¬λ°°ν¬ |`,
|
|
1987
|
+
`| \`@suh-lab server destroy ${branchName}\` | Preview νκ²½ μμ |`,
|
|
1988
|
+
`| \`@suh-lab server status ${branchName}\` | νμ¬ μν νμΈ |`,
|
|
1989
|
+
'',
|
|
1990
|
+
'---',
|
|
1991
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
1992
|
+
].join('\n');
|
|
1993
|
+
|
|
1994
|
+
await github.rest.issues.updateComment({
|
|
1995
|
+
owner: context.repo.owner,
|
|
1996
|
+
repo: context.repo.repo,
|
|
1997
|
+
comment_id: commentId,
|
|
1998
|
+
body: body
|
|
1999
|
+
});
|
|
2000
|
+
|
|
2001
|
+
- name: λΉλ/λ°°ν¬ μ€ν¨ μ μλ¬ μ½λ©νΈ
|
|
2002
|
+
if: failure() && steps.check_branch.outputs.exists == 'true'
|
|
2003
|
+
uses: actions/github-script@v7
|
|
2004
|
+
with:
|
|
2005
|
+
script: |
|
|
2006
|
+
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
2007
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2008
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
2009
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
2010
|
+
|
|
2011
|
+
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
2012
|
+
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
2013
|
+
|
|
2014
|
+
let buildStatus, dockerStatus, deployStatus;
|
|
2015
|
+
let buildTime = '-', dockerTime = '-';
|
|
2016
|
+
|
|
2017
|
+
if (!commentId) {
|
|
2018
|
+
buildStatus = 'β οΈ μ΄κΈ°ν μ€ν¨';
|
|
2019
|
+
dockerStatus = '-';
|
|
2020
|
+
deployStatus = '-';
|
|
2021
|
+
} else if (!buildDuration || buildDuration === '') {
|
|
2022
|
+
buildStatus = 'β μ€ν¨';
|
|
2023
|
+
dockerStatus = 'βΈοΈ λκΈ°';
|
|
2024
|
+
deployStatus = 'βΈοΈ λκΈ°';
|
|
2025
|
+
} else if (!dockerDuration || dockerDuration === '') {
|
|
2026
|
+
buildStatus = 'β
μλ£';
|
|
2027
|
+
dockerStatus = 'β μ€ν¨';
|
|
2028
|
+
deployStatus = 'βΈοΈ λκΈ°';
|
|
2029
|
+
buildTime = buildDuration;
|
|
2030
|
+
} else {
|
|
2031
|
+
buildStatus = 'β
μλ£';
|
|
2032
|
+
dockerStatus = 'β
μλ£';
|
|
2033
|
+
deployStatus = 'β μ€ν¨';
|
|
2034
|
+
buildTime = buildDuration;
|
|
2035
|
+
dockerTime = dockerDuration;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
const body = [
|
|
2039
|
+
'## β Custom Branch Preview λ°°ν¬ μ€ν¨!',
|
|
2040
|
+
'',
|
|
2041
|
+
`**λΈλμΉ**: \`${branchName}\``,
|
|
2042
|
+
'',
|
|
2043
|
+
'| λ¨κ³ | μν | μμ μκ° |',
|
|
2044
|
+
'|------|------|----------|',
|
|
2045
|
+
`| π¨ νλ‘μ νΈ λΉλ | ${buildStatus} | ${buildTime} |`,
|
|
2046
|
+
`| π³ Docker μ΄λ―Έμ§ μμ± | ${dockerStatus} | ${dockerTime} |`,
|
|
2047
|
+
`| π μλ² λ°°ν¬ & Health Check | ${deployStatus} | - |`,
|
|
2048
|
+
'',
|
|
2049
|
+
`**[π λΉλ/λ°°ν¬ λ‘κ·Έ νμΈ](${runUrl})**`,
|
|
2050
|
+
'',
|
|
2051
|
+
'### π κ°λ₯ν μμΈ',
|
|
2052
|
+
'- Gradle λΉλ μ€ν¨',
|
|
2053
|
+
'- Docker μ΄λ―Έμ§ λΉλ μ€ν¨',
|
|
2054
|
+
'- μλ² μ°κ²° λ¬Έμ ',
|
|
2055
|
+
'- 컨ν
μ΄λ μμ μ€ν¨',
|
|
2056
|
+
'',
|
|
2057
|
+
'---',
|
|
2058
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
2059
|
+
].join('\n');
|
|
2060
|
+
|
|
2061
|
+
if (commentId) {
|
|
2062
|
+
await github.rest.issues.updateComment({
|
|
2063
|
+
owner: context.repo.owner,
|
|
2064
|
+
repo: context.repo.repo,
|
|
2065
|
+
comment_id: commentId,
|
|
2066
|
+
body: body
|
|
2067
|
+
});
|
|
2068
|
+
} else {
|
|
2069
|
+
await github.rest.issues.createComment({
|
|
2070
|
+
owner: context.repo.owner,
|
|
2071
|
+
repo: context.repo.repo,
|
|
2072
|
+
issue_number: context.issue.number,
|
|
2073
|
+
body: body
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
destroy-preview-custom-branch:
|
|
2078
|
+
name: Preview μμ (Custom Branch)
|
|
2079
|
+
needs: check-command
|
|
2080
|
+
if: |
|
|
2081
|
+
needs.check-command.outputs.is_valid == 'true' &&
|
|
2082
|
+
needs.check-command.outputs.command == 'destroy' &&
|
|
2083
|
+
needs.check-command.outputs.is_custom_branch == 'true'
|
|
2084
|
+
runs-on: ubuntu-latest
|
|
2085
|
+
steps:
|
|
2086
|
+
- name: λΈλμΉλͺ
μμ 컨ν
μ΄λ suffix μΆμΆ
|
|
2087
|
+
id: extract
|
|
2088
|
+
uses: actions/github-script@v7
|
|
2089
|
+
with:
|
|
2090
|
+
script: |
|
|
2091
|
+
const branchName = '${{ needs.check-command.outputs.custom_branch }}';
|
|
2092
|
+
const issueMatch = branchName.match(/#(\d+)/);
|
|
2093
|
+
|
|
2094
|
+
if (issueMatch) {
|
|
2095
|
+
core.setOutput('container_suffix', `pr-${issueMatch[1]}`);
|
|
2096
|
+
} else {
|
|
2097
|
+
let hash = 0;
|
|
2098
|
+
for (let i = 0; i < branchName.length; i++) {
|
|
2099
|
+
const char = branchName.charCodeAt(i);
|
|
2100
|
+
hash = ((hash << 5) - hash) + char;
|
|
2101
|
+
hash = hash & hash;
|
|
2102
|
+
}
|
|
2103
|
+
const hashStr = Math.abs(hash).toString(16).slice(-6).padStart(6, '0');
|
|
2104
|
+
core.setOutput('container_suffix', `custom-${hashStr}`);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
core.setOutput('branch_name', branchName);
|
|
2108
|
+
|
|
2109
|
+
- name: 컨ν
μ΄λ μμ
|
|
2110
|
+
id: destroy
|
|
2111
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
2112
|
+
with:
|
|
2113
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
2114
|
+
username: ${{ secrets.SERVER_USER }}
|
|
2115
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
2116
|
+
key: ${{ secrets.SSH_KEY }}
|
|
2117
|
+
port: ${{ env.SSH_PORT }}
|
|
2118
|
+
script: |
|
|
2119
|
+
export PATH=$PATH:/usr/local/bin
|
|
2120
|
+
|
|
2121
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
2122
|
+
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
2123
|
+
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
2124
|
+
|
|
2125
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
2126
|
+
echo "ποΈ Custom Branch Preview μμ "
|
|
2127
|
+
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
2128
|
+
|
|
2129
|
+
if docker ps -a --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
2130
|
+
echo "ποΈ μ»¨ν
μ΄λ μμ μ€: ${CONTAINER_NAME}"
|
|
2131
|
+
docker stop ${CONTAINER_NAME} || true
|
|
2132
|
+
docker rm ${CONTAINER_NAME} || true
|
|
2133
|
+
echo "DESTROYED=true"
|
|
2134
|
+
else
|
|
2135
|
+
echo "βΉοΈ 컨ν
μ΄λκ° μ‘΄μ¬νμ§ μμ: ${CONTAINER_NAME}"
|
|
2136
|
+
echo "DESTROYED=false"
|
|
2137
|
+
fi
|
|
2138
|
+
|
|
2139
|
+
- name: μμ νμΈ μ½λ©νΈ
|
|
2140
|
+
uses: actions/github-script@v7
|
|
2141
|
+
with:
|
|
2142
|
+
script: |
|
|
2143
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2144
|
+
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
2145
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
2146
|
+
const containerName = `${projectName}-${containerSuffix}`;
|
|
2147
|
+
|
|
2148
|
+
const body = [
|
|
2149
|
+
'## ποΈ Custom Branch Preview μμ μλ£',
|
|
2150
|
+
'',
|
|
2151
|
+
'| νλͺ© | κ° |',
|
|
2152
|
+
'|------|-----|',
|
|
2153
|
+
`| **λΈλμΉ** | \`${branchName}\` |`,
|
|
2154
|
+
`| **컨ν
μ΄λ** | \`${containerName}\` |`,
|
|
2155
|
+
'',
|
|
2156
|
+
`λ€μ λ°°ν¬νλ €λ©΄: \`@suh-lab server build ${branchName}\``,
|
|
2157
|
+
'',
|
|
2158
|
+
'---',
|
|
2159
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
2160
|
+
].join('\n');
|
|
2161
|
+
|
|
2162
|
+
await github.rest.issues.createComment({
|
|
2163
|
+
owner: context.repo.owner,
|
|
2164
|
+
repo: context.repo.repo,
|
|
2165
|
+
issue_number: context.issue.number,
|
|
2166
|
+
body: body
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
check-status-custom-branch:
|
|
2170
|
+
name: Preview μν νμΈ (Custom Branch)
|
|
2171
|
+
needs: check-command
|
|
2172
|
+
if: |
|
|
2173
|
+
needs.check-command.outputs.is_valid == 'true' &&
|
|
2174
|
+
needs.check-command.outputs.command == 'status' &&
|
|
2175
|
+
needs.check-command.outputs.is_custom_branch == 'true'
|
|
2176
|
+
runs-on: ubuntu-latest
|
|
2177
|
+
steps:
|
|
2178
|
+
- name: λΈλμΉλͺ
μμ 컨ν
μ΄λ suffix μΆμΆ
|
|
2179
|
+
id: extract
|
|
2180
|
+
uses: actions/github-script@v7
|
|
2181
|
+
with:
|
|
2182
|
+
script: |
|
|
2183
|
+
const branchName = '${{ needs.check-command.outputs.custom_branch }}';
|
|
2184
|
+
const issueMatch = branchName.match(/#(\d+)/);
|
|
2185
|
+
|
|
2186
|
+
if (issueMatch) {
|
|
2187
|
+
core.setOutput('container_suffix', `pr-${issueMatch[1]}`);
|
|
2188
|
+
} else {
|
|
2189
|
+
let hash = 0;
|
|
2190
|
+
for (let i = 0; i < branchName.length; i++) {
|
|
2191
|
+
const char = branchName.charCodeAt(i);
|
|
2192
|
+
hash = ((hash << 5) - hash) + char;
|
|
2193
|
+
hash = hash & hash;
|
|
2194
|
+
}
|
|
2195
|
+
const hashStr = Math.abs(hash).toString(16).slice(-6).padStart(6, '0');
|
|
2196
|
+
core.setOutput('container_suffix', `custom-${hashStr}`);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
core.setOutput('branch_name', branchName);
|
|
2200
|
+
|
|
2201
|
+
- name: 컨ν
μ΄λ μν νμΈ
|
|
2202
|
+
id: check_running
|
|
2203
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
2204
|
+
continue-on-error: true
|
|
2205
|
+
with:
|
|
2206
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
2207
|
+
username: ${{ secrets.SERVER_USER }}
|
|
2208
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
2209
|
+
key: ${{ secrets.SSH_KEY }}
|
|
2210
|
+
port: ${{ env.SSH_PORT }}
|
|
2211
|
+
script: |
|
|
2212
|
+
export PATH=$PATH:/usr/local/bin
|
|
2213
|
+
|
|
2214
|
+
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
2215
|
+
CONTAINER_SUFFIX="${{ steps.extract.outputs.container_suffix }}"
|
|
2216
|
+
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
2217
|
+
|
|
2218
|
+
docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"
|
|
2219
|
+
|
|
2220
|
+
- name: μν μ½λ©νΈ μμ±
|
|
2221
|
+
uses: actions/github-script@v7
|
|
2222
|
+
with:
|
|
2223
|
+
script: |
|
|
2224
|
+
const projectName = '${{ env.PROJECT_NAME }}';
|
|
2225
|
+
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
2226
|
+
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
2227
|
+
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
2228
|
+
const previewPort = '${{ env.PREVIEW_PORT }}';
|
|
2229
|
+
|
|
2230
|
+
const containerName = `${projectName}-${containerSuffix}`;
|
|
2231
|
+
const previewUrl = `http://${containerName}.${domainSuffix}:${previewPort}`;
|
|
2232
|
+
const isRunning = '${{ steps.check_running.outcome }}' === 'success';
|
|
2233
|
+
|
|
2234
|
+
let body;
|
|
2235
|
+
if (isRunning) {
|
|
2236
|
+
body = [
|
|
2237
|
+
'## β
Custom Branch Preview νκ²½ μ€ν μ€',
|
|
2238
|
+
'',
|
|
2239
|
+
'| νλͺ© | κ° |',
|
|
2240
|
+
'|------|-----|',
|
|
2241
|
+
`| **λΈλμΉ** | \`${branchName}\` |`,
|
|
2242
|
+
`| **Preview URL** | ${previewUrl} |`,
|
|
2243
|
+
`| **컨ν
μ΄λ** | \`${containerName}\` |`,
|
|
2244
|
+
'| **μν** | π’ Running |',
|
|
2245
|
+
'',
|
|
2246
|
+
'### π λͺ
λ Ήμ΄',
|
|
2247
|
+
'| λͺ
λ Ήμ΄ | μ€λͺ
|',
|
|
2248
|
+
'|--------|------|',
|
|
2249
|
+
`| \`@suh-lab server build ${branchName}\` | μ΅μ 컀λ°μΌλ‘ μ¬λ°°ν¬ |`,
|
|
2250
|
+
`| \`@suh-lab server destroy ${branchName}\` | Preview νκ²½ μμ |`,
|
|
2251
|
+
'',
|
|
2252
|
+
'---',
|
|
2253
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
2254
|
+
].join('\n');
|
|
2255
|
+
} else {
|
|
2256
|
+
body = [
|
|
2257
|
+
'## β Custom Branch Preview νκ²½ μμ',
|
|
2258
|
+
'',
|
|
2259
|
+
'| νλͺ© | κ° |',
|
|
2260
|
+
'|------|-----|',
|
|
2261
|
+
`| **λΈλμΉ** | \`${branchName}\` |`,
|
|
2262
|
+
`| **컨ν
μ΄λ** | \`${containerName}\` |`,
|
|
2263
|
+
'| **μν** | π΄ Not Found |',
|
|
2264
|
+
'',
|
|
2265
|
+
`λ°°ν¬νλ €λ©΄: \`@suh-lab server build ${branchName}\``,
|
|
2266
|
+
'',
|
|
2267
|
+
'---',
|
|
2268
|
+
'*π€ μ΄ λκΈμ Preview μμ€ν
μ μν΄ μλ μμ±λμμ΅λλ€.*'
|
|
2269
|
+
].join('\n');
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
await github.rest.issues.createComment({
|
|
2273
|
+
owner: context.repo.owner,
|
|
2274
|
+
repo: context.repo.repo,
|
|
2275
|
+
issue_number: context.issue.number,
|
|
2276
|
+
body: body
|
|
2277
|
+
});
|