project-auto-wizard 0.13.2 β 0.14.0
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/README.md +1 -1
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +296 -257
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2279 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +64 -61
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +17 -12
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +59 -34
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +17 -10
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +54 -44
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +24 -17
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +85 -89
- package/src/commands/dry-run.js +63 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +45 -35
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +187 -0
- package/src/i18n/catalog/en/copy.js +19 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +187 -0
- package/src/i18n/catalog/ko/copy.js +19 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +128 -94
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +89 -88
|
@@ -1,88 +1,88 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# Spring Boot PR/Issue Preview
|
|
2
|
+
# Spring Boot PR/Issue Preview system (SSH + Docker)
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# APPLICATION_PROD_YML (
|
|
6
|
-
# DOCKERHUB_USERNAME: DockerHub
|
|
7
|
-
# DOCKERHUB_TOKEN: DockerHub
|
|
8
|
-
# SERVER_HOST:
|
|
9
|
-
# SERVER_USER: SSH
|
|
10
|
-
# SERVER_PASSWORD: SSH
|
|
11
|
-
# SSH_KEY: SSH
|
|
4
|
+
# Required GitHub Secrets
|
|
5
|
+
# APPLICATION_PROD_YML (optional): contents of application-prod.yml
|
|
6
|
+
# DOCKERHUB_USERNAME: DockerHub username
|
|
7
|
+
# DOCKERHUB_TOKEN: DockerHub access token
|
|
8
|
+
# SERVER_HOST: deploy server address
|
|
9
|
+
# SERVER_USER: SSH username
|
|
10
|
+
# SERVER_PASSWORD: SSH password (when SSH_AUTH_METHOD=password)
|
|
11
|
+
# SSH_KEY: contents of the SSH private key .pem (when SSH_AUTH_METHOD=key, e.g. AWS EC2)
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
# /wizard server build -
|
|
15
|
-
# /wizard server destroy -
|
|
16
|
-
# /wizard server status -
|
|
13
|
+
# Usage
|
|
14
|
+
# /wizard server build - Build and deploy the PR/issue
|
|
15
|
+
# /wizard server destroy - Delete the preview environment
|
|
16
|
+
# /wizard server status - Check the current status
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
# -
|
|
20
|
-
# -
|
|
21
|
-
# -
|
|
22
|
-
# - Preview URL: http://{
|
|
18
|
+
# Resource naming rules
|
|
19
|
+
# - Container: {PROJECT_NAME}-pr-{PR number}
|
|
20
|
+
# - Image: {DOCKERHUB_USERNAME}/{PROJECT_NAME}:pr-{PR number}
|
|
21
|
+
# - Domain: {PROJECT_NAME}-pr-{PR number}.pr.example.com
|
|
22
|
+
# - Preview URL: http://{domain}:8079
|
|
23
23
|
|
|
24
24
|
name: PROJECT-SPRING-PR-PREVIEW
|
|
25
25
|
|
|
26
|
-
# [
|
|
26
|
+
# [Section 1] Project settings - when reusing this in another project, edit only this section
|
|
27
27
|
env:
|
|
28
|
-
#
|
|
28
|
+
# Unique project identifier (used in container, image and domain names) (edit per project)
|
|
29
29
|
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
30
|
-
#
|
|
30
|
+
# Subdirectory of this project in a monorepo (--paths). Base for the build and Docker context ('.' means the repo root)
|
|
31
31
|
PROJECT_PATH: "." # @wizard auto:project-path
|
|
32
|
-
#
|
|
33
|
-
#
|
|
32
|
+
# In a monorepo (PROJECT_PATH is not '.') '-spring' is appended to image, container and domain names
|
|
33
|
+
# so they do not collide with services of other types in the same repo (e.g. my-app-spring)
|
|
34
34
|
|
|
35
|
-
# Spring Boot
|
|
35
|
+
# Spring Boot build settings (edit per project)
|
|
36
36
|
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
|
|
37
37
|
GRADLE_BUILD_CMD: './gradlew clean build -x test -Dspring.profiles.active=prod'
|
|
38
38
|
JAR_PATH: "__JAR_PATH__" # @wizard ask:build/libs/*.jar
|
|
39
39
|
APPLICATION_YML_PATH: "__APPLICATION_YML_PATH__" # @wizard auto:spring-app-yml-path
|
|
40
40
|
|
|
41
|
-
# Docker
|
|
42
|
-
DOCKERFILE_PATH: './Dockerfile' #
|
|
41
|
+
# Docker settings (edit per project)
|
|
42
|
+
DOCKERFILE_PATH: './Dockerfile' # path relative to PROJECT_PATH
|
|
43
43
|
INTERNAL_PORT: '8080'
|
|
44
44
|
|
|
45
|
-
#
|
|
46
|
-
PROJECT_TARGET_DIR: "" #
|
|
47
|
-
PROJECT_MNT_DIR: "" #
|
|
45
|
+
# Volume mount settings (edit per project; leave empty to skip mounting)
|
|
46
|
+
PROJECT_TARGET_DIR: "" # data directory on the server (e.g. /volume1/projects/myapp/data)
|
|
47
|
+
PROJECT_MNT_DIR: "" # mount path inside the container (e.g. /mnt/myapp)
|
|
48
48
|
|
|
49
|
-
# Traefik &
|
|
49
|
+
# Traefik & preview domain settings (do not change after the environment is set up)
|
|
50
50
|
TRAEFIK_NETWORK: traefik-network
|
|
51
51
|
PREVIEW_DOMAIN_SUFFIX: "__PREVIEW_DOMAIN_SUFFIX__" # @wizard ask:pr.example.com
|
|
52
52
|
PREVIEW_PORT: '8079'
|
|
53
53
|
|
|
54
|
-
# SSH
|
|
55
|
-
# password β SERVER_PASSWORD secret
|
|
56
|
-
# key β SSH_KEY secret
|
|
54
|
+
# SSH auth method: "password" (Synology / regular servers) | "key" (AWS EC2 / .pem)
|
|
55
|
+
# password β uses the SERVER_PASSWORD secret
|
|
56
|
+
# key β uses the SSH_KEY secret (store the whole .pem contents as the secret value)
|
|
57
57
|
SSH_AUTH_METHOD: "password" # @wizard ask:password
|
|
58
58
|
|
|
59
|
-
# SSH
|
|
59
|
+
# SSH port (do not change after the server port is set up)
|
|
60
60
|
SSH_PORT: "__SSH_PORT__" # @wizard ask:2022
|
|
61
61
|
|
|
62
|
-
# Issue Helper
|
|
63
|
-
#
|
|
62
|
+
# Issue Helper comment marker (edit per project)
|
|
63
|
+
# Used to extract the branch name from an issue comment
|
|
64
64
|
ISSUE_HELPER_MARKER: '<!-- project-auto-wizard issue helper -->'
|
|
65
65
|
|
|
66
|
-
# Health
|
|
67
|
-
# - HEALTH_CHECK_PATH: HTTP
|
|
68
|
-
# - HEALTH_CHECK_LOG_PATTERN:
|
|
69
|
-
# - API_DOCS_PATH: API
|
|
66
|
+
# Health check settings (edit per project)
|
|
67
|
+
# - HEALTH_CHECK_PATH: HTTP health check path (empty skips the HTTP check)
|
|
68
|
+
# - HEALTH_CHECK_LOG_PATTERN: log pattern match (fallback when HTTP fails)
|
|
69
|
+
# - API_DOCS_PATH: API docs URL (empty hides it from the deploy comment)
|
|
70
70
|
#
|
|
71
|
-
# Spring Boot
|
|
71
|
+
# Spring Boot defaults:
|
|
72
72
|
# HEALTH_CHECK_PATH: '/actuator/health'
|
|
73
73
|
# HEALTH_CHECK_LOG_PATTERN: 'Started .* in [0-9.]+ seconds'
|
|
74
|
-
# API_DOCS_PATH: '/swagger-ui.html'
|
|
74
|
+
# API_DOCS_PATH: '/swagger-ui.html' or '/swagger-ui/index.html'
|
|
75
75
|
HEALTH_CHECK_PATH: '/actuator/health'
|
|
76
76
|
HEALTH_CHECK_LOG_PATTERN: 'Started .* in [0-9.]+ seconds'
|
|
77
77
|
API_DOCS_PATH: '/docs/swagger'
|
|
78
78
|
|
|
79
79
|
on:
|
|
80
80
|
issue_comment:
|
|
81
|
-
types: [created] # PR
|
|
81
|
+
types: [created] # PR comments + issue comments
|
|
82
82
|
issues:
|
|
83
|
-
types: [closed] #
|
|
83
|
+
types: [closed] # destroy when the issue is closed
|
|
84
84
|
pull_request:
|
|
85
|
-
types: [closed] # PR
|
|
85
|
+
types: [closed] # destroy when the PR is closed
|
|
86
86
|
|
|
87
87
|
permissions:
|
|
88
88
|
contents: read
|
|
@@ -90,9 +90,9 @@ permissions:
|
|
|
90
90
|
issues: write
|
|
91
91
|
|
|
92
92
|
jobs:
|
|
93
|
-
# Job 1:
|
|
93
|
+
# Job 1: parse the command (supports both PR and issue comments)
|
|
94
94
|
check-command:
|
|
95
|
-
name:
|
|
95
|
+
name: Check command
|
|
96
96
|
if: github.event_name == 'issue_comment'
|
|
97
97
|
runs-on: ubuntu-latest
|
|
98
98
|
outputs:
|
|
@@ -102,7 +102,28 @@ jobs:
|
|
|
102
102
|
custom_branch: ${{ steps.parse.outputs.custom_branch }}
|
|
103
103
|
is_custom_branch: ${{ steps.parse.outputs.is_custom_branch }}
|
|
104
104
|
steps:
|
|
105
|
-
|
|
105
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
106
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
107
|
+
- name: Checkout messages
|
|
108
|
+
uses: actions/checkout@v7
|
|
109
|
+
with:
|
|
110
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
111
|
+
sparse-checkout: |
|
|
112
|
+
.github/scripts/messages.py
|
|
113
|
+
version.yml
|
|
114
|
+
sparse-checkout-cone-mode: false
|
|
115
|
+
|
|
116
|
+
- name: Load messages
|
|
117
|
+
run: |
|
|
118
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
119
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
120
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
121
|
+
{
|
|
122
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
123
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
124
|
+
} >> "$GITHUB_ENV"
|
|
125
|
+
|
|
126
|
+
- name: Add π reaction to comment
|
|
106
127
|
if: contains(github.event.comment.body, '/wizard') && contains(github.event.comment.body, 'server')
|
|
107
128
|
uses: actions/github-script@v9
|
|
108
129
|
with:
|
|
@@ -115,33 +136,35 @@ jobs:
|
|
|
115
136
|
content: 'eyes'
|
|
116
137
|
});
|
|
117
138
|
|
|
118
|
-
- name:
|
|
139
|
+
- name: Parse command
|
|
119
140
|
id: parse
|
|
120
141
|
env:
|
|
121
142
|
COMMENT: ${{ github.event.comment.body }}
|
|
122
143
|
IS_PR: ${{ github.event.issue.pull_request != null }}
|
|
123
144
|
run: |
|
|
124
|
-
|
|
145
|
+
m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
|
|
146
|
+
|
|
147
|
+
# Check whether this is a PR or an issue
|
|
125
148
|
if [[ "$IS_PR" == "true" ]]; then
|
|
126
149
|
echo "is_pr=true" >> $GITHUB_OUTPUT
|
|
127
|
-
|
|
150
|
+
m wf_preview.cmd_from_pr
|
|
128
151
|
else
|
|
129
152
|
echo "is_pr=false" >> $GITHUB_OUTPUT
|
|
130
|
-
|
|
153
|
+
m wf_preview.cmd_from_issue
|
|
131
154
|
fi
|
|
132
155
|
|
|
133
|
-
#
|
|
134
|
-
#
|
|
156
|
+
# Parse the command (branch argument supported)
|
|
157
|
+
# Format: /wizard server <command> [branch]
|
|
135
158
|
if [[ "$COMMENT" =~ /wizard[[:space:]]+server[[:space:]]+build([[:space:]]+([^[:space:]]+))? ]]; then
|
|
136
159
|
echo "command=build" >> $GITHUB_OUTPUT
|
|
137
160
|
echo "is_valid=true" >> $GITHUB_OUTPUT
|
|
138
161
|
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
139
162
|
echo "custom_branch=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
|
140
163
|
echo "is_custom_branch=true" >> $GITHUB_OUTPUT
|
|
141
|
-
|
|
164
|
+
m wf_preview.cmd_detected_build_branch branch="${BASH_REMATCH[2]}"
|
|
142
165
|
else
|
|
143
166
|
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
144
|
-
|
|
167
|
+
m wf_preview.cmd_detected_build
|
|
145
168
|
fi
|
|
146
169
|
elif [[ "$COMMENT" =~ /wizard[[:space:]]+server[[:space:]]+destroy([[:space:]]+([^[:space:]]+))? ]]; then
|
|
147
170
|
echo "command=destroy" >> $GITHUB_OUTPUT
|
|
@@ -149,10 +172,10 @@ jobs:
|
|
|
149
172
|
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
150
173
|
echo "custom_branch=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
|
151
174
|
echo "is_custom_branch=true" >> $GITHUB_OUTPUT
|
|
152
|
-
|
|
175
|
+
m wf_preview.cmd_detected_destroy_branch branch="${BASH_REMATCH[2]}"
|
|
153
176
|
else
|
|
154
177
|
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
155
|
-
|
|
178
|
+
m wf_preview.cmd_detected_destroy
|
|
156
179
|
fi
|
|
157
180
|
elif [[ "$COMMENT" =~ /wizard[[:space:]]+server[[:space:]]+status([[:space:]]+([^[:space:]]+))? ]]; then
|
|
158
181
|
echo "command=status" >> $GITHUB_OUTPUT
|
|
@@ -160,19 +183,19 @@ jobs:
|
|
|
160
183
|
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
|
161
184
|
echo "custom_branch=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
|
162
185
|
echo "is_custom_branch=true" >> $GITHUB_OUTPUT
|
|
163
|
-
|
|
186
|
+
m wf_preview.cmd_detected_status_branch branch="${BASH_REMATCH[2]}"
|
|
164
187
|
else
|
|
165
188
|
echo "is_custom_branch=false" >> $GITHUB_OUTPUT
|
|
166
|
-
|
|
189
|
+
m wf_preview.cmd_detected_status
|
|
167
190
|
fi
|
|
168
191
|
else
|
|
169
192
|
echo "is_valid=false" >> $GITHUB_OUTPUT
|
|
170
|
-
|
|
193
|
+
m wf_preview.cmd_not_wizard
|
|
171
194
|
fi
|
|
172
195
|
|
|
173
|
-
# Job 2-1:
|
|
196
|
+
# Job 2-1: build & deploy (triggered from a PR comment)
|
|
174
197
|
build-preview-pr:
|
|
175
|
-
name: Preview
|
|
198
|
+
name: Preview build & deploy (PR)
|
|
176
199
|
needs: check-command
|
|
177
200
|
if: |
|
|
178
201
|
needs.check-command.outputs.is_valid == 'true' &&
|
|
@@ -181,7 +204,51 @@ jobs:
|
|
|
181
204
|
needs.check-command.outputs.is_custom_branch != 'true'
|
|
182
205
|
runs-on: ubuntu-latest
|
|
183
206
|
steps:
|
|
184
|
-
|
|
207
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
208
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
209
|
+
- name: Checkout messages
|
|
210
|
+
uses: actions/checkout@v7
|
|
211
|
+
with:
|
|
212
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
213
|
+
sparse-checkout: |
|
|
214
|
+
.github/scripts/messages.py
|
|
215
|
+
version.yml
|
|
216
|
+
sparse-checkout-cone-mode: false
|
|
217
|
+
|
|
218
|
+
- name: Load messages
|
|
219
|
+
run: |
|
|
220
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
221
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
222
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
223
|
+
{
|
|
224
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
225
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
226
|
+
} >> "$GITHUB_ENV"
|
|
227
|
+
# The deploy script runs on the remote server, where messages.py does not exist:
|
|
228
|
+
# ship the messages it needs as a small shell function (m KEY name=value ...)
|
|
229
|
+
python3 - >> "$GITHUB_ENV" <<'PY'
|
|
230
|
+
import json, os, shlex, subprocess
|
|
231
|
+
script = os.path.join(os.environ["RUNNER_TEMP"], "paw", "messages.py")
|
|
232
|
+
msgs = json.loads(subprocess.check_output(["python3", script, "dump", "wf_preview.r_"], text=True))
|
|
233
|
+
head = ["m() {", ' case "$1" in']
|
|
234
|
+
for key, text in msgs.items():
|
|
235
|
+
head.append(" %s) _t=%s ;;" % (key, shlex.quote(text)))
|
|
236
|
+
tail = r''' *) _t="$1" ;;
|
|
237
|
+
esac
|
|
238
|
+
shift
|
|
239
|
+
while [ "$#" -gt 0 ]; do
|
|
240
|
+
_v=$(printf '%s' "${1#*=}" | sed 's/[\\&|]/\\&/g')
|
|
241
|
+
_t=$(printf '%s' "$_t" | sed "s|{${1%%=*}}|$_v|g")
|
|
242
|
+
shift
|
|
243
|
+
done
|
|
244
|
+
printf '%s\n' "$_t"
|
|
245
|
+
}'''
|
|
246
|
+
print("PAW_REMOTE<<PAW_EOF")
|
|
247
|
+
print("\n".join(head) + "\n" + tail)
|
|
248
|
+
print("PAW_EOF")
|
|
249
|
+
PY
|
|
250
|
+
|
|
251
|
+
- name: Get PR info
|
|
185
252
|
id: pr
|
|
186
253
|
uses: actions/github-script@v9
|
|
187
254
|
with:
|
|
@@ -195,29 +262,32 @@ jobs:
|
|
|
195
262
|
core.setOutput('sha', pr.data.head.sha.substring(0, 7));
|
|
196
263
|
return pr.data.number;
|
|
197
264
|
|
|
198
|
-
#
|
|
199
|
-
- name:
|
|
265
|
+
# Progress comment system
|
|
266
|
+
- name: Create progress comment
|
|
200
267
|
id: progress
|
|
201
268
|
uses: actions/github-script@v9
|
|
202
269
|
with:
|
|
203
270
|
script: |
|
|
271
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
272
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
273
|
+
|
|
204
274
|
const prNumber = context.issue.number;
|
|
205
275
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
206
276
|
const startTime = Date.now();
|
|
207
277
|
|
|
208
278
|
const body = [
|
|
209
|
-
'
|
|
279
|
+
m('wf_preview.title_pr_building'),
|
|
210
280
|
'',
|
|
211
|
-
'
|
|
281
|
+
m('wf_preview.tbl_steps'),
|
|
212
282
|
'|------|------|----------|',
|
|
213
|
-
'
|
|
214
|
-
'
|
|
215
|
-
'
|
|
283
|
+
m('wf_preview.row_build_progress'),
|
|
284
|
+
m('wf_preview.row_image_wait'),
|
|
285
|
+
m('wf_preview.row_deploy_wait'),
|
|
216
286
|
'',
|
|
217
|
-
|
|
287
|
+
m('wf_preview.link_logs', {runUrl}),
|
|
218
288
|
'',
|
|
219
289
|
'---',
|
|
220
|
-
'
|
|
290
|
+
m('wf_preview.footer_progress')
|
|
221
291
|
].join('\n');
|
|
222
292
|
|
|
223
293
|
const { data: comment } = await github.rest.issues.createComment({
|
|
@@ -230,13 +300,13 @@ jobs:
|
|
|
230
300
|
core.setOutput('comment_id', comment.id);
|
|
231
301
|
core.setOutput('start_time', startTime);
|
|
232
302
|
|
|
233
|
-
- name:
|
|
303
|
+
- name: Checkout code
|
|
234
304
|
uses: actions/checkout@v7
|
|
235
305
|
with:
|
|
236
306
|
ref: ${{ steps.pr.outputs.ref }}
|
|
237
307
|
|
|
238
|
-
#
|
|
239
|
-
- name:
|
|
308
|
+
# If a required secret or the Dockerfile is missing, report the missing items and stop instead of surfacing a third-party error such as a docker login failure
|
|
309
|
+
- name: Deploy pre-check (Secrets and Dockerfile)
|
|
240
310
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
241
311
|
env:
|
|
242
312
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
@@ -247,11 +317,13 @@ jobs:
|
|
|
247
317
|
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
248
318
|
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
249
319
|
run: |
|
|
320
|
+
m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
|
|
321
|
+
|
|
250
322
|
MISSING=""
|
|
251
323
|
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
252
324
|
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
253
325
|
done
|
|
254
|
-
# SSH_AUTH_METHOD=key
|
|
326
|
+
# SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
|
|
255
327
|
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
256
328
|
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
257
329
|
else
|
|
@@ -259,98 +331,101 @@ jobs:
|
|
|
259
331
|
fi
|
|
260
332
|
FAILED=false
|
|
261
333
|
if [ -n "$MISSING" ]; then
|
|
262
|
-
echo "::error title
|
|
334
|
+
echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
|
|
263
335
|
FAILED=true
|
|
264
336
|
fi
|
|
265
337
|
if [ ! -f "$DOCKERFILE" ]; then
|
|
266
|
-
echo "::error title=
|
|
338
|
+
echo "::error title=$(m wf_preview.precheck_dockerfile_title)::$(m wf_preview.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
|
|
267
339
|
FAILED=true
|
|
268
340
|
fi
|
|
269
341
|
if [ "$FAILED" = "true" ]; then
|
|
270
342
|
exit 1
|
|
271
343
|
fi
|
|
272
|
-
|
|
344
|
+
m wf_preview.precheck_ok
|
|
273
345
|
|
|
274
|
-
- name: JDK
|
|
346
|
+
- name: Set up JDK
|
|
275
347
|
uses: actions/setup-java@v5
|
|
276
348
|
with:
|
|
277
349
|
distribution: 'temurin'
|
|
278
350
|
java-version: ${{ env.JAVA_VERSION }}
|
|
279
351
|
cache: 'gradle'
|
|
280
352
|
|
|
281
|
-
- name: Gradle
|
|
353
|
+
- name: Set Gradle permissions
|
|
282
354
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
283
355
|
run: chmod +x gradlew
|
|
284
356
|
|
|
285
|
-
# [
|
|
357
|
+
# [Section 2] Project-specific secret files
|
|
286
358
|
#
|
|
287
|
-
#
|
|
359
|
+
# Required (all Spring Boot projects):
|
|
288
360
|
# - application-prod.yml
|
|
289
361
|
#
|
|
290
|
-
#
|
|
362
|
+
# Optional (add or remove per project):
|
|
291
363
|
# - Firebase: firebase-admin-sdk.json, firebase-messaging-sw.js
|
|
292
364
|
# - Vertex AI: gen-lang-client-xxx.json
|
|
293
|
-
# -
|
|
365
|
+
# - Other config files: admin.yml, prompt.yml, etc.
|
|
294
366
|
#
|
|
295
|
-
#
|
|
296
|
-
# 1.
|
|
297
|
-
# 2.
|
|
298
|
-
# 3.
|
|
367
|
+
# When reusing this in another project:
|
|
368
|
+
# 1. Remove the steps you do not need
|
|
369
|
+
# 2. Add the steps you need
|
|
370
|
+
# 3. Adjust the file paths for your project
|
|
299
371
|
|
|
300
|
-
# [
|
|
301
|
-
- name: "[
|
|
372
|
+
# [Required] Spring Boot config file
|
|
373
|
+
- name: "[Required] Create application-prod.yml"
|
|
302
374
|
run: |
|
|
303
375
|
mkdir -p $(dirname ${{ env.APPLICATION_YML_PATH }})
|
|
304
376
|
cat << 'EOF' > ${{ env.APPLICATION_YML_PATH }}
|
|
305
377
|
${{ secrets.APPLICATION_PROD_YML }}
|
|
306
378
|
EOF
|
|
307
379
|
|
|
308
|
-
# [
|
|
309
|
-
# - name: "[
|
|
380
|
+
# [Optional] The steps below are examples. Delete them if unused.
|
|
381
|
+
# - name: "[Optional] Create Vertex AI service account key"
|
|
310
382
|
# env:
|
|
311
383
|
# VERTEX_SA_KEY: ${{ secrets.VERTEX_SA_KEY }}
|
|
312
384
|
# run: |
|
|
313
|
-
# echo "$VERTEX_SA_KEY" | sed 's/\\n/\n/g' >
|
|
385
|
+
# echo "$VERTEX_SA_KEY" | sed 's/\\n/\n/g' > ./<module>/src/main/resources/vertex-ai-key.json
|
|
314
386
|
|
|
315
|
-
# - name: "[
|
|
387
|
+
# - name: "[Optional] Create Firebase Admin SDK key"
|
|
316
388
|
# env:
|
|
317
389
|
# FIREBASE_KEY_JSON: ${{ secrets.FIREBASE_KEY_JSON }}
|
|
318
390
|
# run: |
|
|
319
|
-
# echo "$FIREBASE_KEY_JSON" | sed 's/\\n/\n/g' >
|
|
391
|
+
# echo "$FIREBASE_KEY_JSON" | sed 's/\\n/\n/g' > ./<module>/src/main/resources/firebase-admin-sdk.json
|
|
320
392
|
|
|
321
|
-
# [
|
|
393
|
+
# [End of section 2] Project-specific secret files
|
|
322
394
|
|
|
323
|
-
- name: Gradle
|
|
395
|
+
- name: Gradle build
|
|
324
396
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
325
397
|
run: ${{ env.GRADLE_BUILD_CMD }}
|
|
326
398
|
|
|
327
|
-
- name:
|
|
399
|
+
- name: Progress - build done
|
|
328
400
|
id: build_progress
|
|
329
401
|
uses: actions/github-script@v9
|
|
330
402
|
with:
|
|
331
403
|
script: |
|
|
404
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
405
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
406
|
+
|
|
332
407
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
333
408
|
const startTime = ${{ steps.progress.outputs.start_time }};
|
|
334
409
|
const now = Date.now();
|
|
335
410
|
const elapsed = now - startTime;
|
|
336
411
|
const minutes = Math.floor(elapsed / 60000);
|
|
337
412
|
const seconds = Math.floor((elapsed % 60000) / 1000);
|
|
338
|
-
const buildDuration = minutes > 0 ?
|
|
413
|
+
const buildDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
|
|
339
414
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
340
415
|
|
|
341
416
|
const body = [
|
|
342
|
-
'
|
|
417
|
+
m('wf_preview.title_pr_building'),
|
|
343
418
|
'',
|
|
344
|
-
'
|
|
419
|
+
m('wf_preview.tbl_steps'),
|
|
345
420
|
'|------|------|----------|',
|
|
346
|
-
|
|
347
|
-
'
|
|
348
|
-
'
|
|
421
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
422
|
+
m('wf_preview.row_image_progress'),
|
|
423
|
+
m('wf_preview.row_deploy_wait'),
|
|
349
424
|
'',
|
|
350
|
-
|
|
425
|
+
m('wf_preview.link_logs', {runUrl}),
|
|
351
426
|
'',
|
|
352
427
|
'---',
|
|
353
|
-
'
|
|
428
|
+
m('wf_preview.footer_progress')
|
|
354
429
|
].join('\n');
|
|
355
430
|
|
|
356
431
|
await github.rest.issues.updateComment({
|
|
@@ -363,16 +438,16 @@ jobs:
|
|
|
363
438
|
core.setOutput('build_duration', buildDuration);
|
|
364
439
|
core.setOutput('docker_start', now);
|
|
365
440
|
|
|
366
|
-
- name: Docker
|
|
441
|
+
- name: Log in to Docker Hub
|
|
367
442
|
uses: docker/login-action@v3
|
|
368
443
|
with:
|
|
369
444
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
370
445
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
371
446
|
|
|
372
|
-
- name: Docker Buildx
|
|
447
|
+
- name: Set up Docker Buildx
|
|
373
448
|
uses: docker/setup-buildx-action@v3
|
|
374
449
|
|
|
375
|
-
- name:
|
|
450
|
+
- name: Build & push Docker image
|
|
376
451
|
uses: docker/build-push-action@v5
|
|
377
452
|
with:
|
|
378
453
|
context: ${{ env.PROJECT_PATH }}
|
|
@@ -382,11 +457,14 @@ jobs:
|
|
|
382
457
|
cache-from: type=gha
|
|
383
458
|
cache-to: type=gha,mode=max
|
|
384
459
|
|
|
385
|
-
- name:
|
|
460
|
+
- name: Progress - Docker done
|
|
386
461
|
id: docker_progress
|
|
387
462
|
uses: actions/github-script@v9
|
|
388
463
|
with:
|
|
389
464
|
script: |
|
|
465
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
466
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
467
|
+
|
|
390
468
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
391
469
|
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
392
470
|
const dockerStart = ${{ steps.build_progress.outputs.docker_start }};
|
|
@@ -394,22 +472,22 @@ jobs:
|
|
|
394
472
|
const dockerElapsed = now - dockerStart;
|
|
395
473
|
const minutes = Math.floor(dockerElapsed / 60000);
|
|
396
474
|
const seconds = Math.floor((dockerElapsed % 60000) / 1000);
|
|
397
|
-
const dockerDuration = minutes > 0 ?
|
|
475
|
+
const dockerDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
|
|
398
476
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
399
477
|
|
|
400
478
|
const body = [
|
|
401
|
-
'
|
|
479
|
+
m('wf_preview.title_pr_building'),
|
|
402
480
|
'',
|
|
403
|
-
'
|
|
481
|
+
m('wf_preview.tbl_steps'),
|
|
404
482
|
'|------|------|----------|',
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
'
|
|
483
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
484
|
+
m('wf_preview.row_image_done', {dockerDuration}),
|
|
485
|
+
m('wf_preview.row_deploy_progress'),
|
|
408
486
|
'',
|
|
409
|
-
|
|
487
|
+
m('wf_preview.link_logs', {runUrl}),
|
|
410
488
|
'',
|
|
411
489
|
'---',
|
|
412
|
-
'
|
|
490
|
+
m('wf_preview.footer_progress')
|
|
413
491
|
].join('\n');
|
|
414
492
|
|
|
415
493
|
await github.rest.issues.updateComment({
|
|
@@ -423,7 +501,7 @@ jobs:
|
|
|
423
501
|
core.setOutput('docker_duration', dockerDuration);
|
|
424
502
|
core.setOutput('deploy_start', now);
|
|
425
503
|
|
|
426
|
-
- name:
|
|
504
|
+
- name: Deploy to server
|
|
427
505
|
uses: appleboy/ssh-action@v1.0.3
|
|
428
506
|
with:
|
|
429
507
|
host: ${{ secrets.SERVER_HOST }}
|
|
@@ -432,12 +510,14 @@ jobs:
|
|
|
432
510
|
key: ${{ secrets.SSH_KEY }}
|
|
433
511
|
port: ${{ env.SSH_PORT }}
|
|
434
512
|
script: |
|
|
513
|
+
${{ env.PAW_REMOTE }}
|
|
514
|
+
|
|
435
515
|
set -e
|
|
436
516
|
|
|
437
517
|
export PATH=$PATH:/usr/local/bin
|
|
438
518
|
export PW="${{ secrets.SERVER_PASSWORD }}"
|
|
439
519
|
|
|
440
|
-
#
|
|
520
|
+
# sudo wrapper that depends on the SSH auth method
|
|
441
521
|
SSH_AUTH_METHOD="${{ env.SSH_AUTH_METHOD }}"
|
|
442
522
|
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
443
523
|
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
@@ -445,7 +525,7 @@ jobs:
|
|
|
445
525
|
else
|
|
446
526
|
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
447
527
|
fi
|
|
448
|
-
|
|
528
|
+
m wf_preview.r_ssh_auth ssh_auth_method="${SSH_AUTH_METHOD}"
|
|
449
529
|
|
|
450
530
|
PR_NUMBER=${{ github.event.issue.number }}
|
|
451
531
|
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
@@ -456,18 +536,18 @@ jobs:
|
|
|
456
536
|
TRAEFIK_NETWORK="${{ env.TRAEFIK_NETWORK }}"
|
|
457
537
|
|
|
458
538
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
459
|
-
|
|
539
|
+
m wf_preview.r_deploy_head_pr
|
|
460
540
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
541
|
+
m wf_preview.r_project_name project_name="${PROJECT_NAME}"
|
|
542
|
+
m wf_preview.r_num_pr pr_number="${PR_NUMBER}"
|
|
543
|
+
m wf_preview.r_container_name container_name="${CONTAINER_NAME}"
|
|
544
|
+
m wf_preview.r_domain domain="${DOMAIN}"
|
|
465
545
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
466
546
|
|
|
467
|
-
|
|
547
|
+
m wf_preview.r_image_pulling
|
|
468
548
|
SUDO docker pull "${IMAGE}"
|
|
469
549
|
|
|
470
|
-
|
|
550
|
+
m wf_preview.r_existing_cleanup
|
|
471
551
|
SUDO docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
472
552
|
|
|
473
553
|
VOLUME_OPTS="-v /etc/localtime:/etc/localtime:ro"
|
|
@@ -475,7 +555,7 @@ jobs:
|
|
|
475
555
|
VOLUME_OPTS="$VOLUME_OPTS -v ${{ env.PROJECT_TARGET_DIR }}:${{ env.PROJECT_MNT_DIR }}"
|
|
476
556
|
fi
|
|
477
557
|
|
|
478
|
-
|
|
558
|
+
m wf_preview.r_container_starting
|
|
479
559
|
SUDO docker run -d \
|
|
480
560
|
--name "${CONTAINER_NAME}" \
|
|
481
561
|
--network "${TRAEFIK_NETWORK}" \
|
|
@@ -488,11 +568,11 @@ jobs:
|
|
|
488
568
|
$VOLUME_OPTS \
|
|
489
569
|
"${IMAGE}"
|
|
490
570
|
|
|
491
|
-
# Health
|
|
492
|
-
# 1.
|
|
493
|
-
# 2.
|
|
571
|
+
# Health check (waits up to 120s) - hybrid approach
|
|
572
|
+
# 1. Try the HTTP check when HEALTH_CHECK_PATH is set
|
|
573
|
+
# 2. Fallback: log pattern match (HEALTH_CHECK_LOG_PATTERN)
|
|
494
574
|
echo ""
|
|
495
|
-
|
|
575
|
+
m wf_preview.r_hc_start
|
|
496
576
|
HEALTH_PATH="${{ env.HEALTH_CHECK_PATH }}"
|
|
497
577
|
LOG_PATTERN="${{ env.HEALTH_CHECK_LOG_PATTERN }}"
|
|
498
578
|
MAX_RETRIES=24
|
|
@@ -504,13 +584,13 @@ jobs:
|
|
|
504
584
|
sleep 5
|
|
505
585
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
506
586
|
|
|
507
|
-
# 1.
|
|
587
|
+
# 1. Check the container state
|
|
508
588
|
STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${CONTAINER_NAME}" 2>/dev/null || echo "not_found")
|
|
509
589
|
|
|
510
590
|
if [ "$STATUS" = "exited" ]; then
|
|
511
|
-
|
|
591
|
+
m wf_preview.r_container_exited
|
|
512
592
|
echo ""
|
|
513
|
-
|
|
593
|
+
m wf_preview.r_container_logs
|
|
514
594
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
515
595
|
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
516
596
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
@@ -518,25 +598,25 @@ jobs:
|
|
|
518
598
|
fi
|
|
519
599
|
|
|
520
600
|
if [ "$STATUS" = "running" ]; then
|
|
521
|
-
# 2. HTTP
|
|
601
|
+
# 2. Try the HTTP health check (only when HEALTH_CHECK_PATH is set)
|
|
522
602
|
if [ -n "$HEALTH_PATH" ]; then
|
|
523
603
|
HEALTH=$(SUDO docker exec "${CONTAINER_NAME}" wget -qO- "http://localhost:${INTERNAL_PORT}${HEALTH_PATH}" 2>/dev/null || echo "")
|
|
524
604
|
|
|
525
|
-
# Spring Actuator
|
|
605
|
+
# Accept a Spring Actuator response ({"status":"UP"}) or any HTTP response
|
|
526
606
|
if echo "$HEALTH" | grep -q '"status":"UP"' || [ -n "$HEALTH" ]; then
|
|
527
|
-
|
|
607
|
+
m wf_preview.r_hc_ok_http health_path="${HEALTH_PATH}"
|
|
528
608
|
HEALTH_CHECK_PASSED=true
|
|
529
609
|
HEALTH_CHECK_METHOD="HTTP"
|
|
530
610
|
break
|
|
531
611
|
fi
|
|
532
612
|
fi
|
|
533
613
|
|
|
534
|
-
# 3. HTTP
|
|
614
|
+
# 3. Without an HTTP response, fall back to log pattern matching
|
|
535
615
|
if [ -n "$LOG_PATTERN" ]; then
|
|
536
616
|
STARTED=$(SUDO docker logs --tail 50 "${CONTAINER_NAME}" 2>&1 | grep -E "$LOG_PATTERN" || echo "")
|
|
537
617
|
|
|
538
618
|
if [ -n "$STARTED" ]; then
|
|
539
|
-
|
|
619
|
+
m wf_preview.r_hc_ok_log
|
|
540
620
|
echo " $STARTED"
|
|
541
621
|
HEALTH_CHECK_PASSED=true
|
|
542
622
|
HEALTH_CHECK_METHOD="Log"
|
|
@@ -545,14 +625,14 @@ jobs:
|
|
|
545
625
|
fi
|
|
546
626
|
fi
|
|
547
627
|
|
|
548
|
-
|
|
628
|
+
m wf_preview.r_hc_waiting retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES" status="$STATUS"
|
|
549
629
|
done
|
|
550
630
|
|
|
551
631
|
if [ "$HEALTH_CHECK_PASSED" = "false" ]; then
|
|
552
632
|
echo ""
|
|
553
|
-
|
|
633
|
+
m wf_preview.r_hc_timeout
|
|
554
634
|
echo ""
|
|
555
|
-
|
|
635
|
+
m wf_preview.r_container_logs
|
|
556
636
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
557
637
|
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
558
638
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
@@ -560,13 +640,16 @@ jobs:
|
|
|
560
640
|
fi
|
|
561
641
|
|
|
562
642
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
563
|
-
|
|
643
|
+
m wf_preview.r_hc_done health_check_method="${HEALTH_CHECK_METHOD}"
|
|
564
644
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
565
645
|
|
|
566
|
-
- name:
|
|
646
|
+
- name: Deploy complete comment
|
|
567
647
|
uses: actions/github-script@v9
|
|
568
648
|
with:
|
|
569
649
|
script: |
|
|
650
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
651
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
652
|
+
|
|
570
653
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
571
654
|
const buildDuration = '${{ steps.docker_progress.outputs.build_duration }}';
|
|
572
655
|
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
@@ -575,7 +658,7 @@ jobs:
|
|
|
575
658
|
const deployElapsed = now - deployStart;
|
|
576
659
|
const minutes = Math.floor(deployElapsed / 60000);
|
|
577
660
|
const seconds = Math.floor((deployElapsed % 60000) / 1000);
|
|
578
|
-
const deployDuration = minutes > 0 ?
|
|
661
|
+
const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
|
|
579
662
|
|
|
580
663
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
581
664
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
@@ -586,41 +669,41 @@ jobs:
|
|
|
586
669
|
const previewUrl = `http://${domain}:${previewPort}`;
|
|
587
670
|
const sha = '${{ steps.pr.outputs.sha }}';
|
|
588
671
|
|
|
589
|
-
//
|
|
672
|
+
// Build the preview environment table (the API Docs row is added only when API_DOCS_PATH is set)
|
|
590
673
|
const envRows = [
|
|
591
674
|
`| **Preview URL** | ${previewUrl} |`,
|
|
592
675
|
];
|
|
593
676
|
if (apiDocsPath) {
|
|
594
677
|
envRows.push(`| **API Docs** | ${previewUrl}${apiDocsPath} |`);
|
|
595
678
|
}
|
|
596
|
-
envRows.push(
|
|
597
|
-
envRows.push(
|
|
679
|
+
envRows.push(m('wf_preview.env_container_pr', {projectName, prNumber}));
|
|
680
|
+
envRows.push(m('wf_preview.env_commit', {sha}));
|
|
598
681
|
|
|
599
682
|
const branchName = '${{ steps.pr.outputs.ref }}';
|
|
600
683
|
|
|
601
684
|
const body = [
|
|
602
|
-
'
|
|
685
|
+
m('wf_preview.title_pr_deployed'),
|
|
603
686
|
'',
|
|
604
|
-
'
|
|
687
|
+
m('wf_preview.tbl_steps'),
|
|
605
688
|
'|------|------|----------|',
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
689
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
690
|
+
m('wf_preview.row_image_done', {dockerDuration}),
|
|
691
|
+
m('wf_preview.row_deploy_done', {deployDuration}),
|
|
609
692
|
'',
|
|
610
|
-
'
|
|
611
|
-
'
|
|
693
|
+
m('wf_preview.sec_env'),
|
|
694
|
+
m('wf_preview.tbl_items'),
|
|
612
695
|
'|------|-----|',
|
|
613
696
|
...envRows,
|
|
614
697
|
'',
|
|
615
|
-
'
|
|
616
|
-
'
|
|
698
|
+
m('wf_preview.sec_commands'),
|
|
699
|
+
m('wf_preview.tbl_commands'),
|
|
617
700
|
'|--------|------|',
|
|
618
|
-
'
|
|
619
|
-
'
|
|
620
|
-
'
|
|
701
|
+
m('wf_preview.cmd_build'),
|
|
702
|
+
m('wf_preview.cmd_destroy'),
|
|
703
|
+
m('wf_preview.cmd_status'),
|
|
621
704
|
'',
|
|
622
705
|
'<details>',
|
|
623
|
-
'
|
|
706
|
+
m('wf_preview.cmd_advanced'),
|
|
624
707
|
'',
|
|
625
708
|
'```',
|
|
626
709
|
`/wizard server build ${branchName}`,
|
|
@@ -630,7 +713,7 @@ jobs:
|
|
|
630
713
|
'</details>',
|
|
631
714
|
'',
|
|
632
715
|
'---',
|
|
633
|
-
'
|
|
716
|
+
m('wf_preview.footer_pr')
|
|
634
717
|
].join('\n');
|
|
635
718
|
|
|
636
719
|
await github.rest.issues.updateComment({
|
|
@@ -640,76 +723,79 @@ jobs:
|
|
|
640
723
|
body: body
|
|
641
724
|
});
|
|
642
725
|
|
|
643
|
-
- name:
|
|
726
|
+
- name: Build/deploy failure comment
|
|
644
727
|
if: failure()
|
|
645
728
|
uses: actions/github-script@v9
|
|
646
729
|
with:
|
|
647
730
|
script: |
|
|
731
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
732
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
733
|
+
|
|
648
734
|
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
649
735
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
650
736
|
const prNumber = context.issue.number;
|
|
651
737
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
652
738
|
|
|
653
|
-
//
|
|
739
|
+
// Show where it failed based on progress
|
|
654
740
|
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
655
741
|
const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
|
|
656
742
|
|
|
657
|
-
//
|
|
743
|
+
// Work out where it failed - early-stage failures are distinguished too
|
|
658
744
|
let buildStatus, dockerStatus, deployStatus;
|
|
659
745
|
let buildTime = '-', dockerTime = '-';
|
|
660
746
|
|
|
661
747
|
if (!commentId) {
|
|
662
|
-
//
|
|
663
|
-
buildStatus = '
|
|
748
|
+
// Failed before the progress comment was created (initialization stage)
|
|
749
|
+
buildStatus = m('wf_preview.status_init_failed');
|
|
664
750
|
dockerStatus = '-';
|
|
665
751
|
deployStatus = '-';
|
|
666
752
|
} else if (!buildDuration || buildDuration === '') {
|
|
667
|
-
//
|
|
668
|
-
buildStatus = '
|
|
669
|
-
dockerStatus = '
|
|
670
|
-
deployStatus = '
|
|
753
|
+
// Failed at the build stage
|
|
754
|
+
buildStatus = m('wf_preview.status_failed');
|
|
755
|
+
dockerStatus = m('wf_preview.status_waiting');
|
|
756
|
+
deployStatus = m('wf_preview.status_waiting');
|
|
671
757
|
} else if (!dockerDuration || dockerDuration === '') {
|
|
672
|
-
// Docker
|
|
673
|
-
buildStatus = '
|
|
674
|
-
dockerStatus = '
|
|
675
|
-
deployStatus = '
|
|
758
|
+
// Failed at the Docker stage
|
|
759
|
+
buildStatus = m('wf_preview.status_done');
|
|
760
|
+
dockerStatus = m('wf_preview.status_failed');
|
|
761
|
+
deployStatus = m('wf_preview.status_waiting');
|
|
676
762
|
buildTime = buildDuration;
|
|
677
763
|
} else {
|
|
678
|
-
//
|
|
679
|
-
buildStatus = '
|
|
680
|
-
dockerStatus = '
|
|
681
|
-
deployStatus = '
|
|
764
|
+
// Failed at the deploy stage
|
|
765
|
+
buildStatus = m('wf_preview.status_done');
|
|
766
|
+
dockerStatus = m('wf_preview.status_done');
|
|
767
|
+
deployStatus = m('wf_preview.status_failed');
|
|
682
768
|
buildTime = buildDuration;
|
|
683
769
|
dockerTime = dockerDuration;
|
|
684
770
|
}
|
|
685
771
|
|
|
686
772
|
const body = [
|
|
687
|
-
'
|
|
773
|
+
m('wf_preview.title_pr_failed'),
|
|
688
774
|
'',
|
|
689
|
-
'
|
|
775
|
+
m('wf_preview.tbl_steps'),
|
|
690
776
|
'|------|------|----------|',
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
777
|
+
m('wf_preview.row_build_status', {buildStatus, buildTime}),
|
|
778
|
+
m('wf_preview.row_image_status', {dockerStatus, dockerTime}),
|
|
779
|
+
m('wf_preview.row_deploy_status', {deployStatus}),
|
|
694
780
|
'',
|
|
695
|
-
|
|
781
|
+
m('wf_preview.link_build_logs', {runUrl}),
|
|
696
782
|
'',
|
|
697
|
-
'
|
|
698
|
-
'
|
|
699
|
-
'
|
|
700
|
-
'
|
|
701
|
-
'
|
|
702
|
-
'
|
|
783
|
+
m('wf_preview.sec_causes'),
|
|
784
|
+
m('wf_preview.hint_secrets'),
|
|
785
|
+
m('wf_preview.hint_gradle'),
|
|
786
|
+
m('wf_preview.hint_docker'),
|
|
787
|
+
m('wf_preview.hint_start_spring'),
|
|
788
|
+
m('wf_preview.hint_health_timeout'),
|
|
703
789
|
'',
|
|
704
|
-
'
|
|
705
|
-
'
|
|
706
|
-
'
|
|
790
|
+
m('wf_preview.sec_next'),
|
|
791
|
+
m('wf_preview.next_logs'),
|
|
792
|
+
m('wf_preview.next_retry'),
|
|
707
793
|
'',
|
|
708
794
|
'---',
|
|
709
|
-
'
|
|
795
|
+
m('wf_preview.footer_pr')
|
|
710
796
|
].join('\n');
|
|
711
797
|
|
|
712
|
-
//
|
|
798
|
+
// Update the progress comment if it exists, otherwise create a new one
|
|
713
799
|
if (commentId) {
|
|
714
800
|
await github.rest.issues.updateComment({
|
|
715
801
|
owner: context.repo.owner,
|
|
@@ -726,9 +812,9 @@ jobs:
|
|
|
726
812
|
});
|
|
727
813
|
}
|
|
728
814
|
|
|
729
|
-
# Job 2-2:
|
|
815
|
+
# Job 2-2: extract the branch name from the issue
|
|
730
816
|
get-branch-from-issue:
|
|
731
|
-
name:
|
|
817
|
+
name: Extract branch from issue
|
|
732
818
|
needs: check-command
|
|
733
819
|
if: |
|
|
734
820
|
needs.check-command.outputs.is_valid == 'true' &&
|
|
@@ -740,30 +826,54 @@ jobs:
|
|
|
740
826
|
found: ${{ steps.extract.outputs.found }}
|
|
741
827
|
issue_number: ${{ steps.extract.outputs.issue_number }}
|
|
742
828
|
steps:
|
|
743
|
-
|
|
829
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
830
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
831
|
+
- name: Checkout messages
|
|
832
|
+
uses: actions/checkout@v7
|
|
833
|
+
with:
|
|
834
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
835
|
+
sparse-checkout: |
|
|
836
|
+
.github/scripts/messages.py
|
|
837
|
+
version.yml
|
|
838
|
+
sparse-checkout-cone-mode: false
|
|
839
|
+
|
|
840
|
+
- name: Load messages
|
|
841
|
+
run: |
|
|
842
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
843
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
844
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
845
|
+
{
|
|
846
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
847
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
848
|
+
} >> "$GITHUB_ENV"
|
|
849
|
+
|
|
850
|
+
- name: Extract branch name from issue comment
|
|
744
851
|
id: extract
|
|
745
852
|
uses: actions/github-script@v9
|
|
746
853
|
with:
|
|
747
854
|
script: |
|
|
855
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
856
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
857
|
+
|
|
748
858
|
const issueNumber = context.issue.number;
|
|
749
859
|
const marker = '${{ env.ISSUE_HELPER_MARKER }}';
|
|
750
860
|
|
|
751
|
-
console.log(
|
|
752
|
-
console.log(
|
|
861
|
+
console.log(m('wf_preview.log_search_branch', {issueNumber}));
|
|
862
|
+
console.log(m('wf_preview.log_marker', {marker}));
|
|
753
863
|
|
|
754
|
-
//
|
|
864
|
+
// Fetch all comments of the issue
|
|
755
865
|
const comments = await github.rest.issues.listComments({
|
|
756
866
|
owner: context.repo.owner,
|
|
757
867
|
repo: context.repo.repo,
|
|
758
868
|
issue_number: issueNumber
|
|
759
869
|
});
|
|
760
870
|
|
|
761
|
-
//
|
|
762
|
-
// "###
|
|
763
|
-
const branchRegex = /###\s
|
|
871
|
+
// Branch extraction pattern (same format regardless of the marker)
|
|
872
|
+
// Match the issue helper heading in either language: "### Branch name" (en) or the Korean "### \uBE0C\uB79C\uCE58(\uBA85)" (ko)
|
|
873
|
+
const branchRegex = /###\s*(?:\uBE0C\uB79C\uCE58(?:\uBA85)?|Branch(?: name)?)\s*\n```\s*\n([^\n]+)\s*\n```/i;
|
|
764
874
|
|
|
765
875
|
for (const comment of comments.data) {
|
|
766
|
-
//
|
|
876
|
+
// Find comments that contain the marker
|
|
767
877
|
if (comment.body.includes(marker)) {
|
|
768
878
|
const match = comment.body.match(branchRegex);
|
|
769
879
|
if (match) {
|
|
@@ -771,40 +881,43 @@ jobs:
|
|
|
771
881
|
core.setOutput('branch', branchName);
|
|
772
882
|
core.setOutput('found', 'true');
|
|
773
883
|
core.setOutput('issue_number', issueNumber);
|
|
774
|
-
console.log(
|
|
884
|
+
console.log(m('wf_preview.log_branch_found', {branchName}));
|
|
775
885
|
return;
|
|
776
886
|
}
|
|
777
887
|
}
|
|
778
888
|
}
|
|
779
889
|
|
|
780
|
-
//
|
|
890
|
+
// Branch not found - not an error, handle it gracefully
|
|
781
891
|
core.setOutput('found', 'false');
|
|
782
892
|
core.setOutput('issue_number', issueNumber);
|
|
783
|
-
console.log('
|
|
893
|
+
console.log(m('wf_preview.log_helper_no_branch'));
|
|
784
894
|
|
|
785
|
-
- name:
|
|
895
|
+
- name: Branch not found notice
|
|
786
896
|
if: steps.extract.outputs.found == 'false'
|
|
787
897
|
uses: actions/github-script@v9
|
|
788
898
|
with:
|
|
789
899
|
script: |
|
|
900
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
901
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
902
|
+
|
|
790
903
|
const issueNumber = context.issue.number;
|
|
791
904
|
const marker = '${{ env.ISSUE_HELPER_MARKER }}';
|
|
792
905
|
|
|
793
906
|
const body = [
|
|
794
|
-
'
|
|
907
|
+
m('wf_preview.title_branch_missing_warn'),
|
|
795
908
|
'',
|
|
796
|
-
'
|
|
909
|
+
m('wf_preview.tbl_items'),
|
|
797
910
|
'|------|-----|',
|
|
798
911
|
`| **Issue** | #${issueNumber} |`,
|
|
799
|
-
|
|
912
|
+
m('wf_preview.env_marker', {marker}),
|
|
800
913
|
'',
|
|
801
|
-
'
|
|
802
|
-
'
|
|
803
|
-
'
|
|
804
|
-
'
|
|
914
|
+
m('wf_preview.sec_check'),
|
|
915
|
+
m('wf_preview.chk_helper_exists'),
|
|
916
|
+
m('wf_preview.chk_helper_section'),
|
|
917
|
+
m('wf_preview.chk_helper_marker'),
|
|
805
918
|
'',
|
|
806
919
|
'---',
|
|
807
|
-
'
|
|
920
|
+
m('wf_preview.footer_generic')
|
|
808
921
|
].join('\n');
|
|
809
922
|
|
|
810
923
|
await github.rest.issues.createComment({
|
|
@@ -814,9 +927,9 @@ jobs:
|
|
|
814
927
|
body: body
|
|
815
928
|
});
|
|
816
929
|
|
|
817
|
-
# Job 2-3:
|
|
930
|
+
# Job 2-3: build & deploy (triggered from an issue comment)
|
|
818
931
|
build-preview-issue:
|
|
819
|
-
name: Preview
|
|
932
|
+
name: Preview build & deploy (Issue)
|
|
820
933
|
needs: [check-command, get-branch-from-issue]
|
|
821
934
|
if: |
|
|
822
935
|
needs.check-command.outputs.is_valid == 'true' &&
|
|
@@ -826,15 +939,62 @@ jobs:
|
|
|
826
939
|
needs.get-branch-from-issue.outputs.found == 'true'
|
|
827
940
|
runs-on: ubuntu-latest
|
|
828
941
|
steps:
|
|
829
|
-
|
|
942
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
943
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
944
|
+
- name: Checkout messages
|
|
945
|
+
uses: actions/checkout@v7
|
|
946
|
+
with:
|
|
947
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
948
|
+
sparse-checkout: |
|
|
949
|
+
.github/scripts/messages.py
|
|
950
|
+
version.yml
|
|
951
|
+
sparse-checkout-cone-mode: false
|
|
952
|
+
|
|
953
|
+
- name: Load messages
|
|
954
|
+
run: |
|
|
955
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
956
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
957
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
958
|
+
{
|
|
959
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
960
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
961
|
+
} >> "$GITHUB_ENV"
|
|
962
|
+
# The deploy script runs on the remote server, where messages.py does not exist:
|
|
963
|
+
# ship the messages it needs as a small shell function (m KEY name=value ...)
|
|
964
|
+
python3 - >> "$GITHUB_ENV" <<'PY'
|
|
965
|
+
import json, os, shlex, subprocess
|
|
966
|
+
script = os.path.join(os.environ["RUNNER_TEMP"], "paw", "messages.py")
|
|
967
|
+
msgs = json.loads(subprocess.check_output(["python3", script, "dump", "wf_preview.r_"], text=True))
|
|
968
|
+
head = ["m() {", ' case "$1" in']
|
|
969
|
+
for key, text in msgs.items():
|
|
970
|
+
head.append(" %s) _t=%s ;;" % (key, shlex.quote(text)))
|
|
971
|
+
tail = r''' *) _t="$1" ;;
|
|
972
|
+
esac
|
|
973
|
+
shift
|
|
974
|
+
while [ "$#" -gt 0 ]; do
|
|
975
|
+
_v=$(printf '%s' "${1#*=}" | sed 's/[\\&|]/\\&/g')
|
|
976
|
+
_t=$(printf '%s' "$_t" | sed "s|{${1%%=*}}|$_v|g")
|
|
977
|
+
shift
|
|
978
|
+
done
|
|
979
|
+
printf '%s\n' "$_t"
|
|
980
|
+
}'''
|
|
981
|
+
print("PAW_REMOTE<<PAW_EOF")
|
|
982
|
+
print("\n".join(head) + "\n" + tail)
|
|
983
|
+
print("PAW_EOF")
|
|
984
|
+
PY
|
|
985
|
+
|
|
986
|
+
- name: Check branch exists
|
|
830
987
|
id: check_branch
|
|
831
988
|
uses: actions/github-script@v9
|
|
832
989
|
with:
|
|
833
990
|
script: |
|
|
991
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
992
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
993
|
+
|
|
834
994
|
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
835
995
|
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
836
996
|
|
|
837
|
-
console.log(
|
|
997
|
+
console.log(m('wf_preview.log_check_exists', {branchName}));
|
|
838
998
|
|
|
839
999
|
try {
|
|
840
1000
|
await github.rest.repos.getBranch({
|
|
@@ -843,28 +1003,28 @@ jobs:
|
|
|
843
1003
|
branch: branchName
|
|
844
1004
|
});
|
|
845
1005
|
core.setOutput('exists', 'true');
|
|
846
|
-
console.log(
|
|
1006
|
+
console.log(m('wf_preview.log_branch_exists', {branchName}));
|
|
847
1007
|
} catch (error) {
|
|
848
1008
|
if (error.status === 404) {
|
|
849
1009
|
core.setOutput('exists', 'false');
|
|
850
|
-
console.log(
|
|
1010
|
+
console.log(m('wf_preview.log_branch_missing', {branchName}));
|
|
851
1011
|
|
|
852
|
-
//
|
|
1012
|
+
// Comment for a missing branch
|
|
853
1013
|
const body = [
|
|
854
|
-
'
|
|
1014
|
+
m('wf_preview.title_branch_missing'),
|
|
855
1015
|
'',
|
|
856
|
-
'
|
|
1016
|
+
m('wf_preview.tbl_items'),
|
|
857
1017
|
'|------|-----|',
|
|
858
1018
|
`| **Issue** | #${issueNumber} |`,
|
|
859
|
-
|
|
1019
|
+
m('wf_preview.env_branch', {branchName}),
|
|
860
1020
|
'',
|
|
861
|
-
'
|
|
862
|
-
'
|
|
863
|
-
'
|
|
864
|
-
'
|
|
1021
|
+
m('wf_preview.sec_check'),
|
|
1022
|
+
m('wf_preview.chk_pushed_1'),
|
|
1023
|
+
m('wf_preview.chk_name_2'),
|
|
1024
|
+
m('wf_preview.chk_deleted_3'),
|
|
865
1025
|
'',
|
|
866
1026
|
'---',
|
|
867
|
-
'
|
|
1027
|
+
m('wf_preview.footer_generic')
|
|
868
1028
|
].join('\n');
|
|
869
1029
|
|
|
870
1030
|
await github.rest.issues.createComment({
|
|
@@ -878,47 +1038,52 @@ jobs:
|
|
|
878
1038
|
}
|
|
879
1039
|
}
|
|
880
1040
|
|
|
881
|
-
- name:
|
|
1041
|
+
- name: Skip when branch is missing
|
|
882
1042
|
if: steps.check_branch.outputs.exists == 'false'
|
|
883
1043
|
run: |
|
|
884
|
-
|
|
1044
|
+
m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
|
|
1045
|
+
|
|
1046
|
+
m wf_preview.skip_no_branch
|
|
885
1047
|
exit 0
|
|
886
1048
|
|
|
887
|
-
- name:
|
|
1049
|
+
- name: Set issue info
|
|
888
1050
|
if: steps.check_branch.outputs.exists == 'true'
|
|
889
1051
|
id: issue
|
|
890
1052
|
run: |
|
|
891
1053
|
echo "ref=${{ needs.get-branch-from-issue.outputs.branch_name }}" >> $GITHUB_OUTPUT
|
|
892
1054
|
echo "issue_number=${{ needs.get-branch-from-issue.outputs.issue_number }}" >> $GITHUB_OUTPUT
|
|
893
|
-
#
|
|
1055
|
+
# Get the latest commit SHA of the branch
|
|
894
1056
|
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
|
|
895
1057
|
|
|
896
|
-
- name:
|
|
1058
|
+
- name: Create progress comment
|
|
897
1059
|
if: steps.check_branch.outputs.exists == 'true'
|
|
898
1060
|
id: progress
|
|
899
1061
|
uses: actions/github-script@v9
|
|
900
1062
|
with:
|
|
901
1063
|
script: |
|
|
1064
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1065
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1066
|
+
|
|
902
1067
|
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
903
1068
|
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
904
1069
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
905
1070
|
const startTime = Date.now();
|
|
906
1071
|
|
|
907
1072
|
const body = [
|
|
908
|
-
'
|
|
1073
|
+
m('wf_preview.title_issue_building'),
|
|
909
1074
|
'',
|
|
910
|
-
|
|
1075
|
+
m('wf_preview.line_branch', {branchName}),
|
|
911
1076
|
'',
|
|
912
|
-
'
|
|
1077
|
+
m('wf_preview.tbl_steps'),
|
|
913
1078
|
'|------|------|----------|',
|
|
914
|
-
'
|
|
915
|
-
'
|
|
916
|
-
'
|
|
1079
|
+
m('wf_preview.row_build_progress'),
|
|
1080
|
+
m('wf_preview.row_image_wait'),
|
|
1081
|
+
m('wf_preview.row_deploy_wait'),
|
|
917
1082
|
'',
|
|
918
|
-
|
|
1083
|
+
m('wf_preview.link_logs', {runUrl}),
|
|
919
1084
|
'',
|
|
920
1085
|
'---',
|
|
921
|
-
'
|
|
1086
|
+
m('wf_preview.footer_progress')
|
|
922
1087
|
].join('\n');
|
|
923
1088
|
|
|
924
1089
|
const { data: comment } = await github.rest.issues.createComment({
|
|
@@ -931,14 +1096,14 @@ jobs:
|
|
|
931
1096
|
core.setOutput('comment_id', comment.id);
|
|
932
1097
|
core.setOutput('start_time', startTime);
|
|
933
1098
|
|
|
934
|
-
- name:
|
|
1099
|
+
- name: Checkout code
|
|
935
1100
|
if: steps.check_branch.outputs.exists == 'true'
|
|
936
1101
|
uses: actions/checkout@v7
|
|
937
1102
|
with:
|
|
938
1103
|
ref: ${{ needs.get-branch-from-issue.outputs.branch_name }}
|
|
939
1104
|
|
|
940
|
-
#
|
|
941
|
-
- name:
|
|
1105
|
+
# If a required secret or the Dockerfile is missing, report the missing items and stop instead of surfacing a third-party error such as a docker login failure
|
|
1106
|
+
- name: Deploy pre-check (Secrets and Dockerfile)
|
|
942
1107
|
if: steps.check_branch.outputs.exists == 'true'
|
|
943
1108
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
944
1109
|
env:
|
|
@@ -950,11 +1115,13 @@ jobs:
|
|
|
950
1115
|
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
951
1116
|
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
952
1117
|
run: |
|
|
1118
|
+
m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
|
|
1119
|
+
|
|
953
1120
|
MISSING=""
|
|
954
1121
|
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
955
1122
|
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
956
1123
|
done
|
|
957
|
-
# SSH_AUTH_METHOD=key
|
|
1124
|
+
# SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
|
|
958
1125
|
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
959
1126
|
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
960
1127
|
else
|
|
@@ -962,19 +1129,19 @@ jobs:
|
|
|
962
1129
|
fi
|
|
963
1130
|
FAILED=false
|
|
964
1131
|
if [ -n "$MISSING" ]; then
|
|
965
|
-
echo "::error title
|
|
1132
|
+
echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
|
|
966
1133
|
FAILED=true
|
|
967
1134
|
fi
|
|
968
1135
|
if [ ! -f "$DOCKERFILE" ]; then
|
|
969
|
-
echo "::error title=
|
|
1136
|
+
echo "::error title=$(m wf_preview.precheck_dockerfile_title)::$(m wf_preview.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
|
|
970
1137
|
FAILED=true
|
|
971
1138
|
fi
|
|
972
1139
|
if [ "$FAILED" = "true" ]; then
|
|
973
1140
|
exit 1
|
|
974
1141
|
fi
|
|
975
|
-
|
|
1142
|
+
m wf_preview.precheck_ok
|
|
976
1143
|
|
|
977
|
-
- name: JDK
|
|
1144
|
+
- name: Set up JDK
|
|
978
1145
|
if: steps.check_branch.outputs.exists == 'true'
|
|
979
1146
|
uses: actions/setup-java@v5
|
|
980
1147
|
with:
|
|
@@ -982,32 +1149,35 @@ jobs:
|
|
|
982
1149
|
java-version: ${{ env.JAVA_VERSION }}
|
|
983
1150
|
cache: 'gradle'
|
|
984
1151
|
|
|
985
|
-
- name: Gradle
|
|
1152
|
+
- name: Set Gradle permissions
|
|
986
1153
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
987
1154
|
if: steps.check_branch.outputs.exists == 'true'
|
|
988
1155
|
run: chmod +x gradlew
|
|
989
1156
|
|
|
990
|
-
# [
|
|
991
|
-
- name: "[
|
|
1157
|
+
# [Section 2] Project-specific secret files
|
|
1158
|
+
- name: "[Required] Create application-prod.yml"
|
|
992
1159
|
if: steps.check_branch.outputs.exists == 'true'
|
|
993
1160
|
run: |
|
|
994
1161
|
mkdir -p $(dirname ${{ env.APPLICATION_YML_PATH }})
|
|
995
1162
|
cat << 'EOF' > ${{ env.APPLICATION_YML_PATH }}
|
|
996
1163
|
${{ secrets.APPLICATION_PROD_YML }}
|
|
997
1164
|
EOF
|
|
998
|
-
# [
|
|
1165
|
+
# [End of section 2] Project-specific secret files
|
|
999
1166
|
|
|
1000
|
-
- name: Gradle
|
|
1167
|
+
- name: Gradle build
|
|
1001
1168
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
1002
1169
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1003
1170
|
run: ${{ env.GRADLE_BUILD_CMD }}
|
|
1004
1171
|
|
|
1005
|
-
- name:
|
|
1172
|
+
- name: Progress - build done
|
|
1006
1173
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1007
1174
|
id: build_progress
|
|
1008
1175
|
uses: actions/github-script@v9
|
|
1009
1176
|
with:
|
|
1010
1177
|
script: |
|
|
1178
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1179
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1180
|
+
|
|
1011
1181
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1012
1182
|
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1013
1183
|
const startTime = ${{ steps.progress.outputs.start_time }};
|
|
@@ -1015,24 +1185,24 @@ jobs:
|
|
|
1015
1185
|
const elapsed = now - startTime;
|
|
1016
1186
|
const minutes = Math.floor(elapsed / 60000);
|
|
1017
1187
|
const seconds = Math.floor((elapsed % 60000) / 1000);
|
|
1018
|
-
const buildDuration = minutes > 0 ?
|
|
1188
|
+
const buildDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
|
|
1019
1189
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
1020
1190
|
|
|
1021
1191
|
const body = [
|
|
1022
|
-
'
|
|
1192
|
+
m('wf_preview.title_issue_building'),
|
|
1023
1193
|
'',
|
|
1024
|
-
|
|
1194
|
+
m('wf_preview.line_branch', {branchName}),
|
|
1025
1195
|
'',
|
|
1026
|
-
'
|
|
1196
|
+
m('wf_preview.tbl_steps'),
|
|
1027
1197
|
'|------|------|----------|',
|
|
1028
|
-
|
|
1029
|
-
'
|
|
1030
|
-
'
|
|
1198
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
1199
|
+
m('wf_preview.row_image_progress'),
|
|
1200
|
+
m('wf_preview.row_deploy_wait'),
|
|
1031
1201
|
'',
|
|
1032
|
-
|
|
1202
|
+
m('wf_preview.link_logs', {runUrl}),
|
|
1033
1203
|
'',
|
|
1034
1204
|
'---',
|
|
1035
|
-
'
|
|
1205
|
+
m('wf_preview.footer_progress')
|
|
1036
1206
|
].join('\n');
|
|
1037
1207
|
|
|
1038
1208
|
await github.rest.issues.updateComment({
|
|
@@ -1045,18 +1215,18 @@ jobs:
|
|
|
1045
1215
|
core.setOutput('build_duration', buildDuration);
|
|
1046
1216
|
core.setOutput('docker_start', now);
|
|
1047
1217
|
|
|
1048
|
-
- name: Docker
|
|
1218
|
+
- name: Log in to Docker Hub
|
|
1049
1219
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1050
1220
|
uses: docker/login-action@v3
|
|
1051
1221
|
with:
|
|
1052
1222
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
1053
1223
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
1054
1224
|
|
|
1055
|
-
- name: Docker Buildx
|
|
1225
|
+
- name: Set up Docker Buildx
|
|
1056
1226
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1057
1227
|
uses: docker/setup-buildx-action@v3
|
|
1058
1228
|
|
|
1059
|
-
- name:
|
|
1229
|
+
- name: Build & push Docker image
|
|
1060
1230
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1061
1231
|
uses: docker/build-push-action@v5
|
|
1062
1232
|
with:
|
|
@@ -1067,12 +1237,15 @@ jobs:
|
|
|
1067
1237
|
cache-from: type=gha
|
|
1068
1238
|
cache-to: type=gha,mode=max
|
|
1069
1239
|
|
|
1070
|
-
- name:
|
|
1240
|
+
- name: Progress - Docker done
|
|
1071
1241
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1072
1242
|
id: docker_progress
|
|
1073
1243
|
uses: actions/github-script@v9
|
|
1074
1244
|
with:
|
|
1075
1245
|
script: |
|
|
1246
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1247
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1248
|
+
|
|
1076
1249
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1077
1250
|
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1078
1251
|
const buildDuration = '${{ steps.build_progress.outputs.build_duration }}';
|
|
@@ -1081,24 +1254,24 @@ jobs:
|
|
|
1081
1254
|
const dockerElapsed = now - dockerStart;
|
|
1082
1255
|
const minutes = Math.floor(dockerElapsed / 60000);
|
|
1083
1256
|
const seconds = Math.floor((dockerElapsed % 60000) / 1000);
|
|
1084
|
-
const dockerDuration = minutes > 0 ?
|
|
1257
|
+
const dockerDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
|
|
1085
1258
|
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
1086
1259
|
|
|
1087
1260
|
const body = [
|
|
1088
|
-
'
|
|
1261
|
+
m('wf_preview.title_issue_building'),
|
|
1089
1262
|
'',
|
|
1090
|
-
|
|
1263
|
+
m('wf_preview.line_branch', {branchName}),
|
|
1091
1264
|
'',
|
|
1092
|
-
'
|
|
1265
|
+
m('wf_preview.tbl_steps'),
|
|
1093
1266
|
'|------|------|----------|',
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
'
|
|
1267
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
1268
|
+
m('wf_preview.row_image_done', {dockerDuration}),
|
|
1269
|
+
m('wf_preview.row_deploy_progress'),
|
|
1097
1270
|
'',
|
|
1098
|
-
|
|
1271
|
+
m('wf_preview.link_logs', {runUrl}),
|
|
1099
1272
|
'',
|
|
1100
1273
|
'---',
|
|
1101
|
-
'
|
|
1274
|
+
m('wf_preview.footer_progress')
|
|
1102
1275
|
].join('\n');
|
|
1103
1276
|
|
|
1104
1277
|
await github.rest.issues.updateComment({
|
|
@@ -1112,7 +1285,7 @@ jobs:
|
|
|
1112
1285
|
core.setOutput('docker_duration', dockerDuration);
|
|
1113
1286
|
core.setOutput('deploy_start', now);
|
|
1114
1287
|
|
|
1115
|
-
- name:
|
|
1288
|
+
- name: Deploy to server
|
|
1116
1289
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1117
1290
|
uses: appleboy/ssh-action@v1.0.3
|
|
1118
1291
|
with:
|
|
@@ -1122,12 +1295,14 @@ jobs:
|
|
|
1122
1295
|
key: ${{ secrets.SSH_KEY }}
|
|
1123
1296
|
port: ${{ env.SSH_PORT }}
|
|
1124
1297
|
script: |
|
|
1298
|
+
${{ env.PAW_REMOTE }}
|
|
1299
|
+
|
|
1125
1300
|
set -e
|
|
1126
1301
|
|
|
1127
1302
|
export PATH=$PATH:/usr/local/bin
|
|
1128
1303
|
export PW="${{ secrets.SERVER_PASSWORD }}"
|
|
1129
1304
|
|
|
1130
|
-
#
|
|
1305
|
+
# sudo wrapper that depends on the SSH auth method
|
|
1131
1306
|
SSH_AUTH_METHOD="${{ env.SSH_AUTH_METHOD }}"
|
|
1132
1307
|
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
1133
1308
|
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
@@ -1135,7 +1310,7 @@ jobs:
|
|
|
1135
1310
|
else
|
|
1136
1311
|
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
1137
1312
|
fi
|
|
1138
|
-
|
|
1313
|
+
m wf_preview.r_ssh_auth ssh_auth_method="${SSH_AUTH_METHOD}"
|
|
1139
1314
|
|
|
1140
1315
|
ISSUE_NUMBER=${{ needs.get-branch-from-issue.outputs.issue_number }}
|
|
1141
1316
|
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
@@ -1146,18 +1321,18 @@ jobs:
|
|
|
1146
1321
|
TRAEFIK_NETWORK="${{ env.TRAEFIK_NETWORK }}"
|
|
1147
1322
|
|
|
1148
1323
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1149
|
-
|
|
1324
|
+
m wf_preview.r_deploy_head_issue
|
|
1150
1325
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1326
|
+
m wf_preview.r_project_name project_name="${PROJECT_NAME}"
|
|
1327
|
+
m wf_preview.r_num_issue issue_number="${ISSUE_NUMBER}"
|
|
1328
|
+
m wf_preview.r_container_name container_name="${CONTAINER_NAME}"
|
|
1329
|
+
m wf_preview.r_domain domain="${DOMAIN}"
|
|
1155
1330
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1156
1331
|
|
|
1157
|
-
|
|
1332
|
+
m wf_preview.r_image_pulling
|
|
1158
1333
|
SUDO docker pull "${IMAGE}"
|
|
1159
1334
|
|
|
1160
|
-
|
|
1335
|
+
m wf_preview.r_existing_cleanup
|
|
1161
1336
|
SUDO docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
1162
1337
|
|
|
1163
1338
|
VOLUME_OPTS="-v /etc/localtime:/etc/localtime:ro"
|
|
@@ -1165,7 +1340,7 @@ jobs:
|
|
|
1165
1340
|
VOLUME_OPTS="$VOLUME_OPTS -v ${{ env.PROJECT_TARGET_DIR }}:${{ env.PROJECT_MNT_DIR }}"
|
|
1166
1341
|
fi
|
|
1167
1342
|
|
|
1168
|
-
|
|
1343
|
+
m wf_preview.r_container_starting
|
|
1169
1344
|
SUDO docker run -d \
|
|
1170
1345
|
--name "${CONTAINER_NAME}" \
|
|
1171
1346
|
--network "${TRAEFIK_NETWORK}" \
|
|
@@ -1178,11 +1353,11 @@ jobs:
|
|
|
1178
1353
|
$VOLUME_OPTS \
|
|
1179
1354
|
"${IMAGE}"
|
|
1180
1355
|
|
|
1181
|
-
# Health
|
|
1182
|
-
# 1.
|
|
1183
|
-
# 2.
|
|
1356
|
+
# Health check (waits up to 120s) - hybrid approach
|
|
1357
|
+
# 1. Try the HTTP check when HEALTH_CHECK_PATH is set
|
|
1358
|
+
# 2. Fallback: log pattern match (HEALTH_CHECK_LOG_PATTERN)
|
|
1184
1359
|
echo ""
|
|
1185
|
-
|
|
1360
|
+
m wf_preview.r_hc_start
|
|
1186
1361
|
HEALTH_PATH="${{ env.HEALTH_CHECK_PATH }}"
|
|
1187
1362
|
LOG_PATTERN="${{ env.HEALTH_CHECK_LOG_PATTERN }}"
|
|
1188
1363
|
MAX_RETRIES=24
|
|
@@ -1194,13 +1369,13 @@ jobs:
|
|
|
1194
1369
|
sleep 5
|
|
1195
1370
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
1196
1371
|
|
|
1197
|
-
# 1.
|
|
1372
|
+
# 1. Check the container state
|
|
1198
1373
|
STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${CONTAINER_NAME}" 2>/dev/null || echo "not_found")
|
|
1199
1374
|
|
|
1200
1375
|
if [ "$STATUS" = "exited" ]; then
|
|
1201
|
-
|
|
1376
|
+
m wf_preview.r_container_exited
|
|
1202
1377
|
echo ""
|
|
1203
|
-
|
|
1378
|
+
m wf_preview.r_container_logs
|
|
1204
1379
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1205
1380
|
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
1206
1381
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
@@ -1208,25 +1383,25 @@ jobs:
|
|
|
1208
1383
|
fi
|
|
1209
1384
|
|
|
1210
1385
|
if [ "$STATUS" = "running" ]; then
|
|
1211
|
-
# 2. HTTP
|
|
1386
|
+
# 2. Try the HTTP health check (only when HEALTH_CHECK_PATH is set)
|
|
1212
1387
|
if [ -n "$HEALTH_PATH" ]; then
|
|
1213
1388
|
HEALTH=$(SUDO docker exec "${CONTAINER_NAME}" wget -qO- "http://localhost:${INTERNAL_PORT}${HEALTH_PATH}" 2>/dev/null || echo "")
|
|
1214
1389
|
|
|
1215
|
-
# Spring Actuator
|
|
1390
|
+
# Accept a Spring Actuator response ({"status":"UP"}) or any HTTP response
|
|
1216
1391
|
if echo "$HEALTH" | grep -q '"status":"UP"' || [ -n "$HEALTH" ]; then
|
|
1217
|
-
|
|
1392
|
+
m wf_preview.r_hc_ok_http health_path="${HEALTH_PATH}"
|
|
1218
1393
|
HEALTH_CHECK_PASSED=true
|
|
1219
1394
|
HEALTH_CHECK_METHOD="HTTP"
|
|
1220
1395
|
break
|
|
1221
1396
|
fi
|
|
1222
1397
|
fi
|
|
1223
1398
|
|
|
1224
|
-
# 3. HTTP
|
|
1399
|
+
# 3. Without an HTTP response, fall back to log pattern matching
|
|
1225
1400
|
if [ -n "$LOG_PATTERN" ]; then
|
|
1226
1401
|
STARTED=$(SUDO docker logs --tail 50 "${CONTAINER_NAME}" 2>&1 | grep -E "$LOG_PATTERN" || echo "")
|
|
1227
1402
|
|
|
1228
1403
|
if [ -n "$STARTED" ]; then
|
|
1229
|
-
|
|
1404
|
+
m wf_preview.r_hc_ok_log
|
|
1230
1405
|
echo " $STARTED"
|
|
1231
1406
|
HEALTH_CHECK_PASSED=true
|
|
1232
1407
|
HEALTH_CHECK_METHOD="Log"
|
|
@@ -1235,14 +1410,14 @@ jobs:
|
|
|
1235
1410
|
fi
|
|
1236
1411
|
fi
|
|
1237
1412
|
|
|
1238
|
-
|
|
1413
|
+
m wf_preview.r_hc_waiting retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES" status="$STATUS"
|
|
1239
1414
|
done
|
|
1240
1415
|
|
|
1241
1416
|
if [ "$HEALTH_CHECK_PASSED" = "false" ]; then
|
|
1242
1417
|
echo ""
|
|
1243
|
-
|
|
1418
|
+
m wf_preview.r_hc_timeout
|
|
1244
1419
|
echo ""
|
|
1245
|
-
|
|
1420
|
+
m wf_preview.r_container_logs
|
|
1246
1421
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1247
1422
|
SUDO docker logs --tail 100 "${CONTAINER_NAME}"
|
|
1248
1423
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
@@ -1250,14 +1425,17 @@ jobs:
|
|
|
1250
1425
|
fi
|
|
1251
1426
|
|
|
1252
1427
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1253
|
-
|
|
1428
|
+
m wf_preview.r_hc_done health_check_method="${HEALTH_CHECK_METHOD}"
|
|
1254
1429
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1255
1430
|
|
|
1256
|
-
- name:
|
|
1431
|
+
- name: Deploy complete comment
|
|
1257
1432
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1258
1433
|
uses: actions/github-script@v9
|
|
1259
1434
|
with:
|
|
1260
1435
|
script: |
|
|
1436
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1437
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1438
|
+
|
|
1261
1439
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1262
1440
|
const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
|
|
1263
1441
|
const buildDuration = '${{ steps.docker_progress.outputs.build_duration }}';
|
|
@@ -1267,7 +1445,7 @@ jobs:
|
|
|
1267
1445
|
const deployElapsed = now - deployStart;
|
|
1268
1446
|
const minutes = Math.floor(deployElapsed / 60000);
|
|
1269
1447
|
const seconds = Math.floor((deployElapsed % 60000) / 1000);
|
|
1270
|
-
const deployDuration = minutes > 0 ?
|
|
1448
|
+
const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
|
|
1271
1449
|
|
|
1272
1450
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1273
1451
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
@@ -1278,42 +1456,42 @@ jobs:
|
|
|
1278
1456
|
const previewUrl = `http://${domain}:${previewPort}`;
|
|
1279
1457
|
const sha = '${{ steps.issue.outputs.sha }}';
|
|
1280
1458
|
|
|
1281
|
-
//
|
|
1459
|
+
// Build the preview environment table (the API Docs row is added only when API_DOCS_PATH is set)
|
|
1282
1460
|
const envRows = [
|
|
1283
1461
|
`| **Preview URL** | ${previewUrl} |`,
|
|
1284
1462
|
];
|
|
1285
1463
|
if (apiDocsPath) {
|
|
1286
1464
|
envRows.push(`| **API Docs** | ${previewUrl}${apiDocsPath} |`);
|
|
1287
1465
|
}
|
|
1288
|
-
envRows.push(
|
|
1289
|
-
envRows.push(
|
|
1290
|
-
envRows.push(
|
|
1466
|
+
envRows.push(m('wf_preview.env_container_issue', {projectName, issueNumber}));
|
|
1467
|
+
envRows.push(m('wf_preview.env_branch', {branchName}));
|
|
1468
|
+
envRows.push(m('wf_preview.env_commit', {sha}));
|
|
1291
1469
|
|
|
1292
1470
|
const body = [
|
|
1293
|
-
'
|
|
1471
|
+
m('wf_preview.title_issue_deployed'),
|
|
1294
1472
|
'',
|
|
1295
|
-
|
|
1473
|
+
m('wf_preview.line_branch', {branchName}),
|
|
1296
1474
|
'',
|
|
1297
|
-
'
|
|
1475
|
+
m('wf_preview.tbl_steps'),
|
|
1298
1476
|
'|------|------|----------|',
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1477
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
1478
|
+
m('wf_preview.row_image_done', {dockerDuration}),
|
|
1479
|
+
m('wf_preview.row_deploy_done', {deployDuration}),
|
|
1302
1480
|
'',
|
|
1303
|
-
'
|
|
1304
|
-
'
|
|
1481
|
+
m('wf_preview.sec_env'),
|
|
1482
|
+
m('wf_preview.tbl_items'),
|
|
1305
1483
|
'|------|-----|',
|
|
1306
1484
|
...envRows,
|
|
1307
1485
|
'',
|
|
1308
|
-
'
|
|
1309
|
-
'
|
|
1486
|
+
m('wf_preview.sec_commands'),
|
|
1487
|
+
m('wf_preview.tbl_commands'),
|
|
1310
1488
|
'|--------|------|',
|
|
1311
|
-
'
|
|
1312
|
-
'
|
|
1313
|
-
'
|
|
1489
|
+
m('wf_preview.cmd_build'),
|
|
1490
|
+
m('wf_preview.cmd_destroy'),
|
|
1491
|
+
m('wf_preview.cmd_status'),
|
|
1314
1492
|
'',
|
|
1315
1493
|
'<details>',
|
|
1316
|
-
'
|
|
1494
|
+
m('wf_preview.cmd_advanced'),
|
|
1317
1495
|
'',
|
|
1318
1496
|
'```',
|
|
1319
1497
|
`/wizard server build ${branchName}`,
|
|
@@ -1323,7 +1501,7 @@ jobs:
|
|
|
1323
1501
|
'</details>',
|
|
1324
1502
|
'',
|
|
1325
1503
|
'---',
|
|
1326
|
-
'
|
|
1504
|
+
m('wf_preview.footer_issue')
|
|
1327
1505
|
].join('\n');
|
|
1328
1506
|
|
|
1329
1507
|
await github.rest.issues.updateComment({
|
|
@@ -1333,11 +1511,14 @@ jobs:
|
|
|
1333
1511
|
body: body
|
|
1334
1512
|
});
|
|
1335
1513
|
|
|
1336
|
-
- name:
|
|
1514
|
+
- name: Build/deploy failure comment
|
|
1337
1515
|
if: failure() && steps.check_branch.outputs.exists == 'true'
|
|
1338
1516
|
uses: actions/github-script@v9
|
|
1339
1517
|
with:
|
|
1340
1518
|
script: |
|
|
1519
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1520
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1521
|
+
|
|
1341
1522
|
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
1342
1523
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1343
1524
|
const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
|
|
@@ -1351,52 +1532,52 @@ jobs:
|
|
|
1351
1532
|
let buildTime = '-', dockerTime = '-';
|
|
1352
1533
|
|
|
1353
1534
|
if (!commentId) {
|
|
1354
|
-
buildStatus = '
|
|
1535
|
+
buildStatus = m('wf_preview.status_init_failed');
|
|
1355
1536
|
dockerStatus = '-';
|
|
1356
1537
|
deployStatus = '-';
|
|
1357
1538
|
} else if (!buildDuration || buildDuration === '') {
|
|
1358
|
-
buildStatus = '
|
|
1359
|
-
dockerStatus = '
|
|
1360
|
-
deployStatus = '
|
|
1539
|
+
buildStatus = m('wf_preview.status_failed');
|
|
1540
|
+
dockerStatus = m('wf_preview.status_waiting');
|
|
1541
|
+
deployStatus = m('wf_preview.status_waiting');
|
|
1361
1542
|
} else if (!dockerDuration || dockerDuration === '') {
|
|
1362
|
-
buildStatus = '
|
|
1363
|
-
dockerStatus = '
|
|
1364
|
-
deployStatus = '
|
|
1543
|
+
buildStatus = m('wf_preview.status_done');
|
|
1544
|
+
dockerStatus = m('wf_preview.status_failed');
|
|
1545
|
+
deployStatus = m('wf_preview.status_waiting');
|
|
1365
1546
|
buildTime = buildDuration;
|
|
1366
1547
|
} else {
|
|
1367
|
-
buildStatus = '
|
|
1368
|
-
dockerStatus = '
|
|
1369
|
-
deployStatus = '
|
|
1548
|
+
buildStatus = m('wf_preview.status_done');
|
|
1549
|
+
dockerStatus = m('wf_preview.status_done');
|
|
1550
|
+
deployStatus = m('wf_preview.status_failed');
|
|
1370
1551
|
buildTime = buildDuration;
|
|
1371
1552
|
dockerTime = dockerDuration;
|
|
1372
1553
|
}
|
|
1373
1554
|
|
|
1374
1555
|
const body = [
|
|
1375
|
-
'
|
|
1556
|
+
m('wf_preview.title_issue_failed'),
|
|
1376
1557
|
'',
|
|
1377
|
-
|
|
1558
|
+
m('wf_preview.line_branch', {branchName}),
|
|
1378
1559
|
'',
|
|
1379
|
-
'
|
|
1560
|
+
m('wf_preview.tbl_steps'),
|
|
1380
1561
|
'|------|------|----------|',
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1562
|
+
m('wf_preview.row_build_status', {buildStatus, buildTime}),
|
|
1563
|
+
m('wf_preview.row_image_status', {dockerStatus, dockerTime}),
|
|
1564
|
+
m('wf_preview.row_deploy_status', {deployStatus}),
|
|
1384
1565
|
'',
|
|
1385
|
-
|
|
1566
|
+
m('wf_preview.link_build_logs', {runUrl}),
|
|
1386
1567
|
'',
|
|
1387
|
-
'
|
|
1388
|
-
'
|
|
1389
|
-
'
|
|
1390
|
-
'
|
|
1391
|
-
'
|
|
1392
|
-
'
|
|
1568
|
+
m('wf_preview.sec_causes'),
|
|
1569
|
+
m('wf_preview.hint_secrets'),
|
|
1570
|
+
m('wf_preview.hint_gradle'),
|
|
1571
|
+
m('wf_preview.hint_docker'),
|
|
1572
|
+
m('wf_preview.hint_start_spring'),
|
|
1573
|
+
m('wf_preview.hint_health_timeout'),
|
|
1393
1574
|
'',
|
|
1394
|
-
'
|
|
1395
|
-
'
|
|
1396
|
-
'
|
|
1575
|
+
m('wf_preview.sec_next'),
|
|
1576
|
+
m('wf_preview.next_logs'),
|
|
1577
|
+
m('wf_preview.next_retry'),
|
|
1397
1578
|
'',
|
|
1398
1579
|
'---',
|
|
1399
|
-
'
|
|
1580
|
+
m('wf_preview.footer_issue')
|
|
1400
1581
|
].join('\n');
|
|
1401
1582
|
|
|
1402
1583
|
if (commentId) {
|
|
@@ -1415,17 +1596,61 @@ jobs:
|
|
|
1415
1596
|
});
|
|
1416
1597
|
}
|
|
1417
1598
|
|
|
1418
|
-
# Job 3:
|
|
1599
|
+
# Job 3: destroy the preview (PR/issue closed or destroy command)
|
|
1419
1600
|
destroy-preview:
|
|
1420
|
-
name:
|
|
1421
|
-
# needs
|
|
1601
|
+
name: Destroy preview
|
|
1602
|
+
# No needs dependency: lets this run on its own for PR/issue closed events
|
|
1422
1603
|
if: |
|
|
1423
1604
|
(github.event_name == 'pull_request' && github.event.action == 'closed') ||
|
|
1424
1605
|
(github.event_name == 'issues' && github.event.action == 'closed') ||
|
|
1425
1606
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/wizard') && contains(github.event.comment.body, 'server destroy'))
|
|
1426
1607
|
runs-on: ubuntu-latest
|
|
1427
1608
|
steps:
|
|
1428
|
-
|
|
1609
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
1610
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
1611
|
+
- name: Checkout messages
|
|
1612
|
+
uses: actions/checkout@v7
|
|
1613
|
+
with:
|
|
1614
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
1615
|
+
sparse-checkout: |
|
|
1616
|
+
.github/scripts/messages.py
|
|
1617
|
+
version.yml
|
|
1618
|
+
sparse-checkout-cone-mode: false
|
|
1619
|
+
|
|
1620
|
+
- name: Load messages
|
|
1621
|
+
run: |
|
|
1622
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
1623
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
1624
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
1625
|
+
{
|
|
1626
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
1627
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
1628
|
+
} >> "$GITHUB_ENV"
|
|
1629
|
+
# The deploy script runs on the remote server, where messages.py does not exist:
|
|
1630
|
+
# ship the messages it needs as a small shell function (m KEY name=value ...)
|
|
1631
|
+
python3 - >> "$GITHUB_ENV" <<'PY'
|
|
1632
|
+
import json, os, shlex, subprocess
|
|
1633
|
+
script = os.path.join(os.environ["RUNNER_TEMP"], "paw", "messages.py")
|
|
1634
|
+
msgs = json.loads(subprocess.check_output(["python3", script, "dump", "wf_preview.r_"], text=True))
|
|
1635
|
+
head = ["m() {", ' case "$1" in']
|
|
1636
|
+
for key, text in msgs.items():
|
|
1637
|
+
head.append(" %s) _t=%s ;;" % (key, shlex.quote(text)))
|
|
1638
|
+
tail = r''' *) _t="$1" ;;
|
|
1639
|
+
esac
|
|
1640
|
+
shift
|
|
1641
|
+
while [ "$#" -gt 0 ]; do
|
|
1642
|
+
_v=$(printf '%s' "${1#*=}" | sed 's/[\\&|]/\\&/g')
|
|
1643
|
+
_t=$(printf '%s' "$_t" | sed "s|{${1%%=*}}|$_v|g")
|
|
1644
|
+
shift
|
|
1645
|
+
done
|
|
1646
|
+
printf '%s\n' "$_t"
|
|
1647
|
+
}'''
|
|
1648
|
+
print("PAW_REMOTE<<PAW_EOF")
|
|
1649
|
+
print("\n".join(head) + "\n" + tail)
|
|
1650
|
+
print("PAW_EOF")
|
|
1651
|
+
PY
|
|
1652
|
+
|
|
1653
|
+
- name: Get PR/issue number
|
|
1429
1654
|
id: pr_number
|
|
1430
1655
|
run: |
|
|
1431
1656
|
if [[ "${{ github.event_name }}" == "issue_comment" ]]; then
|
|
@@ -1439,7 +1664,7 @@ jobs:
|
|
|
1439
1664
|
echo "type=issue_closed" >> $GITHUB_OUTPUT
|
|
1440
1665
|
fi
|
|
1441
1666
|
|
|
1442
|
-
- name:
|
|
1667
|
+
- name: Remove container & image
|
|
1443
1668
|
uses: appleboy/ssh-action@v1.0.3
|
|
1444
1669
|
with:
|
|
1445
1670
|
host: ${{ secrets.SERVER_HOST }}
|
|
@@ -1448,10 +1673,12 @@ jobs:
|
|
|
1448
1673
|
key: ${{ secrets.SSH_KEY }}
|
|
1449
1674
|
port: ${{ env.SSH_PORT }}
|
|
1450
1675
|
script: |
|
|
1676
|
+
${{ env.PAW_REMOTE }}
|
|
1677
|
+
|
|
1451
1678
|
export PATH=$PATH:/usr/local/bin
|
|
1452
1679
|
export PW="${{ secrets.SERVER_PASSWORD }}"
|
|
1453
1680
|
|
|
1454
|
-
#
|
|
1681
|
+
# sudo wrapper that depends on the SSH auth method
|
|
1455
1682
|
SSH_AUTH_METHOD="${{ env.SSH_AUTH_METHOD }}"
|
|
1456
1683
|
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
1457
1684
|
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
@@ -1459,7 +1686,7 @@ jobs:
|
|
|
1459
1686
|
else
|
|
1460
1687
|
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
1461
1688
|
fi
|
|
1462
|
-
|
|
1689
|
+
m wf_preview.r_ssh_auth ssh_auth_method="${SSH_AUTH_METHOD}"
|
|
1463
1690
|
|
|
1464
1691
|
PR_NUMBER=${{ steps.pr_number.outputs.number }}
|
|
1465
1692
|
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
@@ -1467,21 +1694,21 @@ jobs:
|
|
|
1467
1694
|
IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}:pr-${PR_NUMBER}"
|
|
1468
1695
|
|
|
1469
1696
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1470
|
-
|
|
1697
|
+
m wf_preview.r_destroy_head
|
|
1471
1698
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1472
|
-
|
|
1473
|
-
|
|
1699
|
+
m wf_preview.r_project_name project_name="${PROJECT_NAME}"
|
|
1700
|
+
m wf_preview.r_num_generic pr_number="${PR_NUMBER}"
|
|
1474
1701
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1475
1702
|
|
|
1476
|
-
|
|
1703
|
+
m wf_preview.r_container_removing
|
|
1477
1704
|
SUDO docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
1478
1705
|
|
|
1479
|
-
|
|
1706
|
+
m wf_preview.r_image_removing
|
|
1480
1707
|
SUDO docker rmi "${IMAGE}" 2>/dev/null || true
|
|
1481
1708
|
|
|
1482
|
-
|
|
1709
|
+
m wf_preview.r_removed
|
|
1483
1710
|
|
|
1484
|
-
- name:
|
|
1711
|
+
- name: Destroy complete comment
|
|
1485
1712
|
if: |
|
|
1486
1713
|
github.event_name == 'issue_comment' ||
|
|
1487
1714
|
(github.event_name == 'pull_request' && github.event.action == 'closed') ||
|
|
@@ -1489,21 +1716,24 @@ jobs:
|
|
|
1489
1716
|
uses: actions/github-script@v9
|
|
1490
1717
|
with:
|
|
1491
1718
|
script: |
|
|
1719
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1720
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1721
|
+
|
|
1492
1722
|
const number = ${{ steps.pr_number.outputs.number }};
|
|
1493
1723
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1494
1724
|
|
|
1495
1725
|
const body = [
|
|
1496
|
-
'
|
|
1726
|
+
m('wf_preview.title_destroyed'),
|
|
1497
1727
|
'',
|
|
1498
|
-
'
|
|
1728
|
+
m('wf_preview.tbl_items'),
|
|
1499
1729
|
'|------|-----|',
|
|
1500
|
-
|
|
1501
|
-
'
|
|
1730
|
+
m('wf_preview.env_container_num', {projectName, number}),
|
|
1731
|
+
m('wf_preview.env_status_deleted'),
|
|
1502
1732
|
'',
|
|
1503
|
-
'
|
|
1733
|
+
m('wf_preview.redeploy_hint'),
|
|
1504
1734
|
'',
|
|
1505
1735
|
'---',
|
|
1506
|
-
'
|
|
1736
|
+
m('wf_preview.footer_pr_issue')
|
|
1507
1737
|
].join('\n');
|
|
1508
1738
|
|
|
1509
1739
|
await github.rest.issues.createComment({
|
|
@@ -1513,14 +1743,58 @@ jobs:
|
|
|
1513
1743
|
body: body
|
|
1514
1744
|
});
|
|
1515
1745
|
|
|
1516
|
-
# Job 4:
|
|
1746
|
+
# Job 4: status check (PR and issue)
|
|
1517
1747
|
check-status:
|
|
1518
|
-
name: Preview
|
|
1748
|
+
name: Preview status
|
|
1519
1749
|
needs: check-command
|
|
1520
1750
|
if: needs.check-command.outputs.is_valid == 'true' && needs.check-command.outputs.command == 'status'
|
|
1521
1751
|
runs-on: ubuntu-latest
|
|
1522
1752
|
steps:
|
|
1523
|
-
|
|
1753
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
1754
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
1755
|
+
- name: Checkout messages
|
|
1756
|
+
uses: actions/checkout@v7
|
|
1757
|
+
with:
|
|
1758
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
1759
|
+
sparse-checkout: |
|
|
1760
|
+
.github/scripts/messages.py
|
|
1761
|
+
version.yml
|
|
1762
|
+
sparse-checkout-cone-mode: false
|
|
1763
|
+
|
|
1764
|
+
- name: Load messages
|
|
1765
|
+
run: |
|
|
1766
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
1767
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
1768
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
1769
|
+
{
|
|
1770
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
1771
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
1772
|
+
} >> "$GITHUB_ENV"
|
|
1773
|
+
# The deploy script runs on the remote server, where messages.py does not exist:
|
|
1774
|
+
# ship the messages it needs as a small shell function (m KEY name=value ...)
|
|
1775
|
+
python3 - >> "$GITHUB_ENV" <<'PY'
|
|
1776
|
+
import json, os, shlex, subprocess
|
|
1777
|
+
script = os.path.join(os.environ["RUNNER_TEMP"], "paw", "messages.py")
|
|
1778
|
+
msgs = json.loads(subprocess.check_output(["python3", script, "dump", "wf_preview.r_"], text=True))
|
|
1779
|
+
head = ["m() {", ' case "$1" in']
|
|
1780
|
+
for key, text in msgs.items():
|
|
1781
|
+
head.append(" %s) _t=%s ;;" % (key, shlex.quote(text)))
|
|
1782
|
+
tail = r''' *) _t="$1" ;;
|
|
1783
|
+
esac
|
|
1784
|
+
shift
|
|
1785
|
+
while [ "$#" -gt 0 ]; do
|
|
1786
|
+
_v=$(printf '%s' "${1#*=}" | sed 's/[\\&|]/\\&/g')
|
|
1787
|
+
_t=$(printf '%s' "$_t" | sed "s|{${1%%=*}}|$_v|g")
|
|
1788
|
+
shift
|
|
1789
|
+
done
|
|
1790
|
+
printf '%s\n' "$_t"
|
|
1791
|
+
}'''
|
|
1792
|
+
print("PAW_REMOTE<<PAW_EOF")
|
|
1793
|
+
print("\n".join(head) + "\n" + tail)
|
|
1794
|
+
print("PAW_EOF")
|
|
1795
|
+
PY
|
|
1796
|
+
|
|
1797
|
+
- name: Check container status
|
|
1524
1798
|
id: status
|
|
1525
1799
|
uses: appleboy/ssh-action@v1.0.3
|
|
1526
1800
|
with:
|
|
@@ -1530,6 +1804,8 @@ jobs:
|
|
|
1530
1804
|
key: ${{ secrets.SSH_KEY }}
|
|
1531
1805
|
port: ${{ env.SSH_PORT }}
|
|
1532
1806
|
script: |
|
|
1807
|
+
${{ env.PAW_REMOTE }}
|
|
1808
|
+
|
|
1533
1809
|
export PATH=$PATH:/usr/local/bin
|
|
1534
1810
|
|
|
1535
1811
|
NUMBER=${{ github.event.issue.number }}
|
|
@@ -1537,19 +1813,19 @@ jobs:
|
|
|
1537
1813
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
|
|
1538
1814
|
|
|
1539
1815
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1540
|
-
|
|
1816
|
+
m wf_preview.r_status_head
|
|
1541
1817
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1542
1818
|
|
|
1543
1819
|
if docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1544
1820
|
echo "STATUS=running"
|
|
1545
|
-
|
|
1821
|
+
m wf_preview.r_container_running
|
|
1546
1822
|
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.RunningFor}}"
|
|
1547
1823
|
else
|
|
1548
1824
|
echo "STATUS=not_found"
|
|
1549
|
-
|
|
1825
|
+
m wf_preview.r_container_absent
|
|
1550
1826
|
fi
|
|
1551
1827
|
|
|
1552
|
-
- name:
|
|
1828
|
+
- name: Status check (running)
|
|
1553
1829
|
uses: appleboy/ssh-action@v1.0.3
|
|
1554
1830
|
id: check_running
|
|
1555
1831
|
continue-on-error: true
|
|
@@ -1567,10 +1843,13 @@ jobs:
|
|
|
1567
1843
|
CONTAINER_NAME="${PROJECT_NAME}-pr-${NUMBER}"
|
|
1568
1844
|
docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"
|
|
1569
1845
|
|
|
1570
|
-
- name:
|
|
1846
|
+
- name: Post status comment
|
|
1571
1847
|
uses: actions/github-script@v9
|
|
1572
1848
|
with:
|
|
1573
1849
|
script: |
|
|
1850
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
1851
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
1852
|
+
|
|
1574
1853
|
const number = context.issue.number;
|
|
1575
1854
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1576
1855
|
const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
|
|
@@ -1584,36 +1863,36 @@ jobs:
|
|
|
1584
1863
|
let body;
|
|
1585
1864
|
if (isRunning) {
|
|
1586
1865
|
body = [
|
|
1587
|
-
'
|
|
1866
|
+
m('wf_preview.title_running'),
|
|
1588
1867
|
'',
|
|
1589
|
-
'
|
|
1868
|
+
m('wf_preview.tbl_items'),
|
|
1590
1869
|
'|------|-----|',
|
|
1591
1870
|
`| **Preview URL** | ${previewUrl} |`,
|
|
1592
|
-
|
|
1593
|
-
'
|
|
1871
|
+
m('wf_preview.env_container_num', {projectName, number}),
|
|
1872
|
+
m('wf_preview.env_status_running'),
|
|
1594
1873
|
'',
|
|
1595
|
-
'
|
|
1596
|
-
'
|
|
1874
|
+
m('wf_preview.sec_commands'),
|
|
1875
|
+
m('wf_preview.tbl_commands'),
|
|
1597
1876
|
'|--------|------|',
|
|
1598
|
-
'
|
|
1599
|
-
'
|
|
1877
|
+
m('wf_preview.cmd_build'),
|
|
1878
|
+
m('wf_preview.cmd_destroy'),
|
|
1600
1879
|
'',
|
|
1601
1880
|
'---',
|
|
1602
|
-
|
|
1881
|
+
m('wf_preview.footer_context', {contextType})
|
|
1603
1882
|
].join('\n');
|
|
1604
1883
|
} else {
|
|
1605
1884
|
body = [
|
|
1606
|
-
'
|
|
1885
|
+
m('wf_preview.title_absent'),
|
|
1607
1886
|
'',
|
|
1608
|
-
'
|
|
1887
|
+
m('wf_preview.tbl_items'),
|
|
1609
1888
|
'|------|-----|',
|
|
1610
|
-
|
|
1611
|
-
'
|
|
1889
|
+
m('wf_preview.env_container_num', {projectName, number}),
|
|
1890
|
+
m('wf_preview.env_status_absent'),
|
|
1612
1891
|
'',
|
|
1613
|
-
'
|
|
1892
|
+
m('wf_preview.deploy_hint'),
|
|
1614
1893
|
'',
|
|
1615
1894
|
'---',
|
|
1616
|
-
|
|
1895
|
+
m('wf_preview.footer_context', {contextType})
|
|
1617
1896
|
].join('\n');
|
|
1618
1897
|
}
|
|
1619
1898
|
|
|
@@ -1625,11 +1904,11 @@ jobs:
|
|
|
1625
1904
|
});
|
|
1626
1905
|
|
|
1627
1906
|
# Custom Branch Preview Jobs
|
|
1628
|
-
#
|
|
1629
|
-
#
|
|
1907
|
+
# Build, destroy or check status for a specific branch
|
|
1908
|
+
# Command: /wizard server build <branch>
|
|
1630
1909
|
|
|
1631
1910
|
build-preview-custom-branch:
|
|
1632
|
-
name: Preview
|
|
1911
|
+
name: Preview build & deploy (Custom Branch)
|
|
1633
1912
|
needs: check-command
|
|
1634
1913
|
if: |
|
|
1635
1914
|
needs.check-command.outputs.is_valid == 'true' &&
|
|
@@ -1637,7 +1916,51 @@ jobs:
|
|
|
1637
1916
|
needs.check-command.outputs.is_custom_branch == 'true'
|
|
1638
1917
|
runs-on: ubuntu-latest
|
|
1639
1918
|
steps:
|
|
1640
|
-
|
|
1919
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
1920
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
1921
|
+
- name: Checkout messages
|
|
1922
|
+
uses: actions/checkout@v7
|
|
1923
|
+
with:
|
|
1924
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
1925
|
+
sparse-checkout: |
|
|
1926
|
+
.github/scripts/messages.py
|
|
1927
|
+
version.yml
|
|
1928
|
+
sparse-checkout-cone-mode: false
|
|
1929
|
+
|
|
1930
|
+
- name: Load messages
|
|
1931
|
+
run: |
|
|
1932
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
1933
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
1934
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
1935
|
+
{
|
|
1936
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
1937
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
1938
|
+
} >> "$GITHUB_ENV"
|
|
1939
|
+
# The deploy script runs on the remote server, where messages.py does not exist:
|
|
1940
|
+
# ship the messages it needs as a small shell function (m KEY name=value ...)
|
|
1941
|
+
python3 - >> "$GITHUB_ENV" <<'PY'
|
|
1942
|
+
import json, os, shlex, subprocess
|
|
1943
|
+
script = os.path.join(os.environ["RUNNER_TEMP"], "paw", "messages.py")
|
|
1944
|
+
msgs = json.loads(subprocess.check_output(["python3", script, "dump", "wf_preview.r_"], text=True))
|
|
1945
|
+
head = ["m() {", ' case "$1" in']
|
|
1946
|
+
for key, text in msgs.items():
|
|
1947
|
+
head.append(" %s) _t=%s ;;" % (key, shlex.quote(text)))
|
|
1948
|
+
tail = r''' *) _t="$1" ;;
|
|
1949
|
+
esac
|
|
1950
|
+
shift
|
|
1951
|
+
while [ "$#" -gt 0 ]; do
|
|
1952
|
+
_v=$(printf '%s' "${1#*=}" | sed 's/[\\&|]/\\&/g')
|
|
1953
|
+
_t=$(printf '%s' "$_t" | sed "s|{${1%%=*}}|$_v|g")
|
|
1954
|
+
shift
|
|
1955
|
+
done
|
|
1956
|
+
printf '%s\n' "$_t"
|
|
1957
|
+
}'''
|
|
1958
|
+
print("PAW_REMOTE<<PAW_EOF")
|
|
1959
|
+
print("\n".join(head) + "\n" + tail)
|
|
1960
|
+
print("PAW_EOF")
|
|
1961
|
+
PY
|
|
1962
|
+
|
|
1963
|
+
- name: Extract issue number from branch name
|
|
1641
1964
|
id: extract
|
|
1642
1965
|
uses: actions/github-script@v9
|
|
1643
1966
|
with:
|
|
@@ -1650,7 +1973,7 @@ jobs:
|
|
|
1650
1973
|
core.setOutput('use_hash', 'false');
|
|
1651
1974
|
core.setOutput('container_suffix', `pr-${issueMatch[1]}`);
|
|
1652
1975
|
} else {
|
|
1653
|
-
//
|
|
1976
|
+
// Generate a hash
|
|
1654
1977
|
let hash = 0;
|
|
1655
1978
|
for (let i = 0; i < branchName.length; i++) {
|
|
1656
1979
|
const char = branchName.charCodeAt(i);
|
|
@@ -1664,7 +1987,7 @@ jobs:
|
|
|
1664
1987
|
|
|
1665
1988
|
core.setOutput('branch_name', branchName);
|
|
1666
1989
|
|
|
1667
|
-
- name:
|
|
1990
|
+
- name: Check branch exists
|
|
1668
1991
|
id: check_branch
|
|
1669
1992
|
uses: actions/github-script@v9
|
|
1670
1993
|
with:
|
|
@@ -1685,27 +2008,30 @@ jobs:
|
|
|
1685
2008
|
}
|
|
1686
2009
|
}
|
|
1687
2010
|
|
|
1688
|
-
- name:
|
|
2011
|
+
- name: Branch not found error comment
|
|
1689
2012
|
if: steps.check_branch.outputs.exists == 'false'
|
|
1690
2013
|
uses: actions/github-script@v9
|
|
1691
2014
|
with:
|
|
1692
2015
|
script: |
|
|
2016
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2017
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2018
|
+
|
|
1693
2019
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1694
2020
|
|
|
1695
2021
|
const body = [
|
|
1696
|
-
'
|
|
2022
|
+
m('wf_preview.title_branch_missing'),
|
|
1697
2023
|
'',
|
|
1698
|
-
'
|
|
2024
|
+
m('wf_preview.tbl_items'),
|
|
1699
2025
|
'|------|-----|',
|
|
1700
|
-
|
|
2026
|
+
m('wf_preview.env_branch', {branchName}),
|
|
1701
2027
|
'',
|
|
1702
|
-
'
|
|
1703
|
-
'
|
|
1704
|
-
'
|
|
1705
|
-
'
|
|
2028
|
+
m('wf_preview.sec_check'),
|
|
2029
|
+
m('wf_preview.chk_name_1'),
|
|
2030
|
+
m('wf_preview.chk_pushed_2'),
|
|
2031
|
+
m('wf_preview.chk_deleted_3'),
|
|
1706
2032
|
'',
|
|
1707
2033
|
'---',
|
|
1708
|
-
'
|
|
2034
|
+
m('wf_preview.footer_generic')
|
|
1709
2035
|
].join('\n');
|
|
1710
2036
|
|
|
1711
2037
|
await github.rest.issues.createComment({
|
|
@@ -1715,16 +2041,16 @@ jobs:
|
|
|
1715
2041
|
body: body
|
|
1716
2042
|
});
|
|
1717
2043
|
|
|
1718
|
-
core.setFailed('
|
|
2044
|
+
core.setFailed(m('wf_preview.fail_branch_missing_named', {branchName}));
|
|
1719
2045
|
|
|
1720
|
-
- name:
|
|
2046
|
+
- name: Checkout
|
|
1721
2047
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1722
2048
|
uses: actions/checkout@v7
|
|
1723
2049
|
with:
|
|
1724
2050
|
ref: ${{ steps.extract.outputs.branch_name }}
|
|
1725
2051
|
|
|
1726
|
-
#
|
|
1727
|
-
- name:
|
|
2052
|
+
# If a required secret or the Dockerfile is missing, report the missing items and stop instead of surfacing a third-party error such as a docker login failure
|
|
2053
|
+
- name: Deploy pre-check (Secrets and Dockerfile)
|
|
1728
2054
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1729
2055
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
1730
2056
|
env:
|
|
@@ -1736,11 +2062,13 @@ jobs:
|
|
|
1736
2062
|
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
1737
2063
|
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
1738
2064
|
run: |
|
|
2065
|
+
m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
|
|
2066
|
+
|
|
1739
2067
|
MISSING=""
|
|
1740
2068
|
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
1741
2069
|
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
1742
2070
|
done
|
|
1743
|
-
# SSH_AUTH_METHOD=key
|
|
2071
|
+
# SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
|
|
1744
2072
|
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
1745
2073
|
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
1746
2074
|
else
|
|
@@ -1748,48 +2076,51 @@ jobs:
|
|
|
1748
2076
|
fi
|
|
1749
2077
|
FAILED=false
|
|
1750
2078
|
if [ -n "$MISSING" ]; then
|
|
1751
|
-
echo "::error title
|
|
2079
|
+
echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
|
|
1752
2080
|
FAILED=true
|
|
1753
2081
|
fi
|
|
1754
2082
|
if [ ! -f "$DOCKERFILE" ]; then
|
|
1755
|
-
echo "::error title=
|
|
2083
|
+
echo "::error title=$(m wf_preview.precheck_dockerfile_title)::$(m wf_preview.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
|
|
1756
2084
|
FAILED=true
|
|
1757
2085
|
fi
|
|
1758
2086
|
if [ "$FAILED" = "true" ]; then
|
|
1759
2087
|
exit 1
|
|
1760
2088
|
fi
|
|
1761
|
-
|
|
2089
|
+
m wf_preview.precheck_ok
|
|
1762
2090
|
|
|
1763
|
-
- name:
|
|
2091
|
+
- name: Get latest commit info
|
|
1764
2092
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1765
2093
|
id: commit
|
|
1766
2094
|
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
1767
2095
|
|
|
1768
|
-
- name:
|
|
2096
|
+
- name: Create progress comment
|
|
1769
2097
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1770
2098
|
id: progress
|
|
1771
2099
|
uses: actions/github-script@v9
|
|
1772
2100
|
with:
|
|
1773
2101
|
script: |
|
|
2102
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2103
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2104
|
+
|
|
1774
2105
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1775
2106
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
1776
2107
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
1777
2108
|
const sha = '${{ steps.commit.outputs.sha }}';
|
|
1778
2109
|
|
|
1779
2110
|
const body = [
|
|
1780
|
-
'
|
|
2111
|
+
m('wf_preview.title_custom_started'),
|
|
1781
2112
|
'',
|
|
1782
|
-
|
|
1783
|
-
|
|
2113
|
+
m('wf_preview.line_branch', {branchName}),
|
|
2114
|
+
m('wf_preview.line_commit', {sha}),
|
|
1784
2115
|
'',
|
|
1785
|
-
'
|
|
2116
|
+
m('wf_preview.tbl_steps'),
|
|
1786
2117
|
'|------|------|----------|',
|
|
1787
|
-
'
|
|
1788
|
-
'
|
|
1789
|
-
'
|
|
2118
|
+
m('wf_preview.row_build_progress'),
|
|
2119
|
+
m('wf_preview.row_image_wait'),
|
|
2120
|
+
m('wf_preview.row_deploy_wait'),
|
|
1790
2121
|
'',
|
|
1791
2122
|
'---',
|
|
1792
|
-
'
|
|
2123
|
+
m('wf_preview.footer_generic')
|
|
1793
2124
|
].join('\n');
|
|
1794
2125
|
|
|
1795
2126
|
const comment = await github.rest.issues.createComment({
|
|
@@ -1801,14 +2132,14 @@ jobs:
|
|
|
1801
2132
|
|
|
1802
2133
|
core.setOutput('comment_id', comment.data.id);
|
|
1803
2134
|
|
|
1804
|
-
- name: JDK
|
|
2135
|
+
- name: Set up JDK
|
|
1805
2136
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1806
2137
|
uses: actions/setup-java@v5
|
|
1807
2138
|
with:
|
|
1808
2139
|
java-version: ${{ env.JAVA_VERSION }}
|
|
1809
2140
|
distribution: 'temurin'
|
|
1810
2141
|
|
|
1811
|
-
- name: Gradle
|
|
2142
|
+
- name: Cache Gradle
|
|
1812
2143
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1813
2144
|
uses: actions/cache@v6
|
|
1814
2145
|
with:
|
|
@@ -1819,7 +2150,7 @@ jobs:
|
|
|
1819
2150
|
restore-keys: |
|
|
1820
2151
|
${{ runner.os }}-gradle-
|
|
1821
2152
|
|
|
1822
|
-
- name: Gradle
|
|
2153
|
+
- name: Gradle build
|
|
1823
2154
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
1824
2155
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1825
2156
|
id: build
|
|
@@ -1831,12 +2162,15 @@ jobs:
|
|
|
1831
2162
|
BUILD_DURATION=$((BUILD_END - BUILD_START))
|
|
1832
2163
|
echo "duration=${BUILD_DURATION}s" >> $GITHUB_OUTPUT
|
|
1833
2164
|
|
|
1834
|
-
- name:
|
|
2165
|
+
- name: Update progress (build done)
|
|
1835
2166
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1836
2167
|
id: build_progress
|
|
1837
2168
|
uses: actions/github-script@v9
|
|
1838
2169
|
with:
|
|
1839
2170
|
script: |
|
|
2171
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2172
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2173
|
+
|
|
1840
2174
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1841
2175
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1842
2176
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
@@ -1844,19 +2178,19 @@ jobs:
|
|
|
1844
2178
|
const buildDuration = '${{ steps.build.outputs.duration }}';
|
|
1845
2179
|
|
|
1846
2180
|
const body = [
|
|
1847
|
-
'
|
|
2181
|
+
m('wf_preview.title_custom_started'),
|
|
1848
2182
|
'',
|
|
1849
|
-
|
|
1850
|
-
|
|
2183
|
+
m('wf_preview.line_branch', {branchName}),
|
|
2184
|
+
m('wf_preview.line_commit', {sha}),
|
|
1851
2185
|
'',
|
|
1852
|
-
'
|
|
2186
|
+
m('wf_preview.tbl_steps'),
|
|
1853
2187
|
'|------|------|----------|',
|
|
1854
|
-
|
|
1855
|
-
'
|
|
1856
|
-
'
|
|
2188
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
2189
|
+
m('wf_preview.row_image_progress'),
|
|
2190
|
+
m('wf_preview.row_deploy_wait'),
|
|
1857
2191
|
'',
|
|
1858
2192
|
'---',
|
|
1859
|
-
'
|
|
2193
|
+
m('wf_preview.footer_generic')
|
|
1860
2194
|
].join('\n');
|
|
1861
2195
|
|
|
1862
2196
|
await github.rest.issues.updateComment({
|
|
@@ -1868,14 +2202,14 @@ jobs:
|
|
|
1868
2202
|
|
|
1869
2203
|
core.setOutput('build_duration', buildDuration);
|
|
1870
2204
|
|
|
1871
|
-
- name: Docker Hub
|
|
2205
|
+
- name: Log in to Docker Hub
|
|
1872
2206
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1873
2207
|
uses: docker/login-action@v3
|
|
1874
2208
|
with:
|
|
1875
2209
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
1876
2210
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
1877
2211
|
|
|
1878
|
-
- name:
|
|
2212
|
+
- name: Build & push Docker image
|
|
1879
2213
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
1880
2214
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1881
2215
|
id: docker
|
|
@@ -1890,12 +2224,15 @@ jobs:
|
|
|
1890
2224
|
echo "duration=${DOCKER_DURATION}s" >> $GITHUB_OUTPUT
|
|
1891
2225
|
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
|
|
1892
2226
|
|
|
1893
|
-
- name:
|
|
2227
|
+
- name: Update progress (Docker done)
|
|
1894
2228
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1895
2229
|
id: docker_progress
|
|
1896
2230
|
uses: actions/github-script@v9
|
|
1897
2231
|
with:
|
|
1898
2232
|
script: |
|
|
2233
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2234
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2235
|
+
|
|
1899
2236
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
1900
2237
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
1901
2238
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
@@ -1904,19 +2241,19 @@ jobs:
|
|
|
1904
2241
|
const dockerDuration = '${{ steps.docker.outputs.duration }}';
|
|
1905
2242
|
|
|
1906
2243
|
const body = [
|
|
1907
|
-
'
|
|
2244
|
+
m('wf_preview.title_custom_started'),
|
|
1908
2245
|
'',
|
|
1909
|
-
|
|
1910
|
-
|
|
2246
|
+
m('wf_preview.line_branch', {branchName}),
|
|
2247
|
+
m('wf_preview.line_commit', {sha}),
|
|
1911
2248
|
'',
|
|
1912
|
-
'
|
|
2249
|
+
m('wf_preview.tbl_steps'),
|
|
1913
2250
|
'|------|------|----------|',
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
'
|
|
2251
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
2252
|
+
m('wf_preview.row_image_done', {dockerDuration}),
|
|
2253
|
+
m('wf_preview.row_deploy_progress'),
|
|
1917
2254
|
'',
|
|
1918
2255
|
'---',
|
|
1919
|
-
'
|
|
2256
|
+
m('wf_preview.footer_generic')
|
|
1920
2257
|
].join('\n');
|
|
1921
2258
|
|
|
1922
2259
|
await github.rest.issues.updateComment({
|
|
@@ -1928,7 +2265,7 @@ jobs:
|
|
|
1928
2265
|
|
|
1929
2266
|
core.setOutput('docker_duration', dockerDuration);
|
|
1930
2267
|
|
|
1931
|
-
- name:
|
|
2268
|
+
- name: Deploy to server
|
|
1932
2269
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1933
2270
|
id: deploy
|
|
1934
2271
|
uses: appleboy/ssh-action@v1.0.3
|
|
@@ -1939,6 +2276,8 @@ jobs:
|
|
|
1939
2276
|
key: ${{ secrets.SSH_KEY }}
|
|
1940
2277
|
port: ${{ env.SSH_PORT }}
|
|
1941
2278
|
script: |
|
|
2279
|
+
${{ env.PAW_REMOTE }}
|
|
2280
|
+
|
|
1942
2281
|
export PATH=$PATH:/usr/local/bin
|
|
1943
2282
|
|
|
1944
2283
|
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
@@ -1949,18 +2288,18 @@ jobs:
|
|
|
1949
2288
|
DOMAIN_SUFFIX="${{ env.PREVIEW_DOMAIN_SUFFIX }}"
|
|
1950
2289
|
|
|
1951
2290
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1952
|
-
|
|
2291
|
+
m wf_preview.r_deploy_head_custom_short
|
|
1953
2292
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
1954
2293
|
|
|
1955
2294
|
docker pull $IMAGE_TAG
|
|
1956
2295
|
|
|
1957
2296
|
if docker ps -a --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1958
|
-
|
|
2297
|
+
m wf_preview.r_existing_removed container_name="${CONTAINER_NAME}"
|
|
1959
2298
|
docker stop ${CONTAINER_NAME} || true
|
|
1960
2299
|
docker rm ${CONTAINER_NAME} || true
|
|
1961
2300
|
fi
|
|
1962
2301
|
|
|
1963
|
-
|
|
2302
|
+
m wf_preview.r_container_new_named container_name="${CONTAINER_NAME}"
|
|
1964
2303
|
docker run -d \
|
|
1965
2304
|
--name ${CONTAINER_NAME} \
|
|
1966
2305
|
--network traefik-network \
|
|
@@ -1970,34 +2309,40 @@ jobs:
|
|
|
1970
2309
|
-l "traefik.http.services.${CONTAINER_NAME}.loadbalancer.server.port=${PREVIEW_PORT}" \
|
|
1971
2310
|
${IMAGE_TAG}
|
|
1972
2311
|
|
|
1973
|
-
|
|
2312
|
+
m wf_preview.r_hc_wait_60
|
|
1974
2313
|
for i in {1..12}; do
|
|
1975
2314
|
if docker ps --filter "name=${CONTAINER_NAME}" --filter "status=running" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1976
|
-
|
|
2315
|
+
m wf_preview.r_container_confirmed
|
|
1977
2316
|
break
|
|
1978
2317
|
fi
|
|
1979
|
-
|
|
2318
|
+
m wf_preview.r_wait_i i="$i"
|
|
1980
2319
|
sleep 5
|
|
1981
2320
|
done
|
|
1982
2321
|
|
|
1983
2322
|
if ! docker ps --filter "name=${CONTAINER_NAME}" --filter "status=running" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
1984
|
-
|
|
2323
|
+
m wf_preview.r_deploy_failed_not_running
|
|
1985
2324
|
exit 1
|
|
1986
2325
|
fi
|
|
1987
2326
|
|
|
1988
|
-
|
|
2327
|
+
m wf_preview.r_deploy_done
|
|
1989
2328
|
echo "π Preview URL: http://${CONTAINER_NAME}.${DOMAIN_SUFFIX}:${PREVIEW_PORT}"
|
|
1990
2329
|
|
|
1991
|
-
- name:
|
|
2330
|
+
- name: Record deploy completion
|
|
1992
2331
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1993
2332
|
id: deploy_time
|
|
1994
|
-
run:
|
|
2333
|
+
run: |
|
|
2334
|
+
m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
|
|
2335
|
+
|
|
2336
|
+
echo "duration=$(m wf_preview.done_label)" >> $GITHUB_OUTPUT
|
|
1995
2337
|
|
|
1996
|
-
- name:
|
|
2338
|
+
- name: Deploy complete comment
|
|
1997
2339
|
if: steps.check_branch.outputs.exists == 'true'
|
|
1998
2340
|
uses: actions/github-script@v9
|
|
1999
2341
|
with:
|
|
2000
2342
|
script: |
|
|
2343
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2344
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2345
|
+
|
|
2001
2346
|
const commentId = ${{ steps.progress.outputs.comment_id }};
|
|
2002
2347
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
2003
2348
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
@@ -2020,35 +2365,35 @@ jobs:
|
|
|
2020
2365
|
if (apiDocsPath) {
|
|
2021
2366
|
envRows.push(`| **API Docs** | ${previewUrl}${apiDocsPath} |`);
|
|
2022
2367
|
}
|
|
2023
|
-
envRows.push(
|
|
2024
|
-
envRows.push(
|
|
2025
|
-
envRows.push(
|
|
2368
|
+
envRows.push(m('wf_preview.env_container_name', {containerName}));
|
|
2369
|
+
envRows.push(m('wf_preview.env_branch', {branchName}));
|
|
2370
|
+
envRows.push(m('wf_preview.env_commit', {sha}));
|
|
2026
2371
|
|
|
2027
2372
|
const body = [
|
|
2028
|
-
'
|
|
2373
|
+
m('wf_preview.title_custom_deployed'),
|
|
2029
2374
|
'',
|
|
2030
|
-
|
|
2375
|
+
m('wf_preview.line_branch', {branchName}),
|
|
2031
2376
|
'',
|
|
2032
|
-
'
|
|
2377
|
+
m('wf_preview.tbl_steps'),
|
|
2033
2378
|
'|------|------|----------|',
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2379
|
+
m('wf_preview.row_build_done', {buildDuration}),
|
|
2380
|
+
m('wf_preview.row_image_done', {dockerDuration}),
|
|
2381
|
+
m('wf_preview.row_deploy_done', {deployDuration}),
|
|
2037
2382
|
'',
|
|
2038
|
-
'
|
|
2039
|
-
'
|
|
2383
|
+
m('wf_preview.sec_env'),
|
|
2384
|
+
m('wf_preview.tbl_items'),
|
|
2040
2385
|
'|------|-----|',
|
|
2041
2386
|
...envRows,
|
|
2042
2387
|
'',
|
|
2043
|
-
'
|
|
2044
|
-
'
|
|
2388
|
+
m('wf_preview.sec_commands'),
|
|
2389
|
+
m('wf_preview.tbl_commands'),
|
|
2045
2390
|
'|--------|------|',
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2391
|
+
m('wf_preview.cmd_build_branch', {branchName}),
|
|
2392
|
+
m('wf_preview.cmd_destroy_branch', {branchName}),
|
|
2393
|
+
m('wf_preview.cmd_status_branch', {branchName}),
|
|
2049
2394
|
'',
|
|
2050
2395
|
'---',
|
|
2051
|
-
'
|
|
2396
|
+
m('wf_preview.footer_generic')
|
|
2052
2397
|
].join('\n');
|
|
2053
2398
|
|
|
2054
2399
|
await github.rest.issues.updateComment({
|
|
@@ -2058,11 +2403,14 @@ jobs:
|
|
|
2058
2403
|
body: body
|
|
2059
2404
|
});
|
|
2060
2405
|
|
|
2061
|
-
- name:
|
|
2406
|
+
- name: Build/deploy failure comment
|
|
2062
2407
|
if: failure() && steps.check_branch.outputs.exists == 'true'
|
|
2063
2408
|
uses: actions/github-script@v9
|
|
2064
2409
|
with:
|
|
2065
2410
|
script: |
|
|
2411
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2412
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2413
|
+
|
|
2066
2414
|
const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
|
|
2067
2415
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
2068
2416
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
@@ -2075,48 +2423,48 @@ jobs:
|
|
|
2075
2423
|
let buildTime = '-', dockerTime = '-';
|
|
2076
2424
|
|
|
2077
2425
|
if (!commentId) {
|
|
2078
|
-
buildStatus = '
|
|
2426
|
+
buildStatus = m('wf_preview.status_init_failed');
|
|
2079
2427
|
dockerStatus = '-';
|
|
2080
2428
|
deployStatus = '-';
|
|
2081
2429
|
} else if (!buildDuration || buildDuration === '') {
|
|
2082
|
-
buildStatus = '
|
|
2083
|
-
dockerStatus = '
|
|
2084
|
-
deployStatus = '
|
|
2430
|
+
buildStatus = m('wf_preview.status_failed');
|
|
2431
|
+
dockerStatus = m('wf_preview.status_waiting');
|
|
2432
|
+
deployStatus = m('wf_preview.status_waiting');
|
|
2085
2433
|
} else if (!dockerDuration || dockerDuration === '') {
|
|
2086
|
-
buildStatus = '
|
|
2087
|
-
dockerStatus = '
|
|
2088
|
-
deployStatus = '
|
|
2434
|
+
buildStatus = m('wf_preview.status_done');
|
|
2435
|
+
dockerStatus = m('wf_preview.status_failed');
|
|
2436
|
+
deployStatus = m('wf_preview.status_waiting');
|
|
2089
2437
|
buildTime = buildDuration;
|
|
2090
2438
|
} else {
|
|
2091
|
-
buildStatus = '
|
|
2092
|
-
dockerStatus = '
|
|
2093
|
-
deployStatus = '
|
|
2439
|
+
buildStatus = m('wf_preview.status_done');
|
|
2440
|
+
dockerStatus = m('wf_preview.status_done');
|
|
2441
|
+
deployStatus = m('wf_preview.status_failed');
|
|
2094
2442
|
buildTime = buildDuration;
|
|
2095
2443
|
dockerTime = dockerDuration;
|
|
2096
2444
|
}
|
|
2097
2445
|
|
|
2098
2446
|
const body = [
|
|
2099
|
-
'
|
|
2447
|
+
m('wf_preview.title_custom_failed'),
|
|
2100
2448
|
'',
|
|
2101
|
-
|
|
2449
|
+
m('wf_preview.line_branch', {branchName}),
|
|
2102
2450
|
'',
|
|
2103
|
-
'
|
|
2451
|
+
m('wf_preview.tbl_steps'),
|
|
2104
2452
|
'|------|------|----------|',
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2453
|
+
m('wf_preview.row_build_status', {buildStatus, buildTime}),
|
|
2454
|
+
m('wf_preview.row_image_status', {dockerStatus, dockerTime}),
|
|
2455
|
+
m('wf_preview.row_deploy_status', {deployStatus}),
|
|
2108
2456
|
'',
|
|
2109
|
-
|
|
2457
|
+
m('wf_preview.link_build_logs', {runUrl}),
|
|
2110
2458
|
'',
|
|
2111
|
-
'
|
|
2112
|
-
'
|
|
2113
|
-
'
|
|
2114
|
-
'
|
|
2115
|
-
'
|
|
2116
|
-
'
|
|
2459
|
+
m('wf_preview.sec_causes'),
|
|
2460
|
+
m('wf_preview.hint_secrets'),
|
|
2461
|
+
m('wf_preview.hint_gradle'),
|
|
2462
|
+
m('wf_preview.hint_docker'),
|
|
2463
|
+
m('wf_preview.hint_server_conn'),
|
|
2464
|
+
m('wf_preview.hint_start'),
|
|
2117
2465
|
'',
|
|
2118
2466
|
'---',
|
|
2119
|
-
'
|
|
2467
|
+
m('wf_preview.footer_generic')
|
|
2120
2468
|
].join('\n');
|
|
2121
2469
|
|
|
2122
2470
|
if (commentId) {
|
|
@@ -2136,7 +2484,7 @@ jobs:
|
|
|
2136
2484
|
}
|
|
2137
2485
|
|
|
2138
2486
|
destroy-preview-custom-branch:
|
|
2139
|
-
name:
|
|
2487
|
+
name: Destroy preview (Custom Branch)
|
|
2140
2488
|
needs: check-command
|
|
2141
2489
|
if: |
|
|
2142
2490
|
needs.check-command.outputs.is_valid == 'true' &&
|
|
@@ -2144,7 +2492,51 @@ jobs:
|
|
|
2144
2492
|
needs.check-command.outputs.is_custom_branch == 'true'
|
|
2145
2493
|
runs-on: ubuntu-latest
|
|
2146
2494
|
steps:
|
|
2147
|
-
|
|
2495
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
2496
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
2497
|
+
- name: Checkout messages
|
|
2498
|
+
uses: actions/checkout@v7
|
|
2499
|
+
with:
|
|
2500
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
2501
|
+
sparse-checkout: |
|
|
2502
|
+
.github/scripts/messages.py
|
|
2503
|
+
version.yml
|
|
2504
|
+
sparse-checkout-cone-mode: false
|
|
2505
|
+
|
|
2506
|
+
- name: Load messages
|
|
2507
|
+
run: |
|
|
2508
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
2509
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
2510
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
2511
|
+
{
|
|
2512
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
2513
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
2514
|
+
} >> "$GITHUB_ENV"
|
|
2515
|
+
# The deploy script runs on the remote server, where messages.py does not exist:
|
|
2516
|
+
# ship the messages it needs as a small shell function (m KEY name=value ...)
|
|
2517
|
+
python3 - >> "$GITHUB_ENV" <<'PY'
|
|
2518
|
+
import json, os, shlex, subprocess
|
|
2519
|
+
script = os.path.join(os.environ["RUNNER_TEMP"], "paw", "messages.py")
|
|
2520
|
+
msgs = json.loads(subprocess.check_output(["python3", script, "dump", "wf_preview.r_"], text=True))
|
|
2521
|
+
head = ["m() {", ' case "$1" in']
|
|
2522
|
+
for key, text in msgs.items():
|
|
2523
|
+
head.append(" %s) _t=%s ;;" % (key, shlex.quote(text)))
|
|
2524
|
+
tail = r''' *) _t="$1" ;;
|
|
2525
|
+
esac
|
|
2526
|
+
shift
|
|
2527
|
+
while [ "$#" -gt 0 ]; do
|
|
2528
|
+
_v=$(printf '%s' "${1#*=}" | sed 's/[\\&|]/\\&/g')
|
|
2529
|
+
_t=$(printf '%s' "$_t" | sed "s|{${1%%=*}}|$_v|g")
|
|
2530
|
+
shift
|
|
2531
|
+
done
|
|
2532
|
+
printf '%s\n' "$_t"
|
|
2533
|
+
}'''
|
|
2534
|
+
print("PAW_REMOTE<<PAW_EOF")
|
|
2535
|
+
print("\n".join(head) + "\n" + tail)
|
|
2536
|
+
print("PAW_EOF")
|
|
2537
|
+
PY
|
|
2538
|
+
|
|
2539
|
+
- name: Extract container suffix from branch name
|
|
2148
2540
|
id: extract
|
|
2149
2541
|
uses: actions/github-script@v9
|
|
2150
2542
|
with:
|
|
@@ -2167,7 +2559,7 @@ jobs:
|
|
|
2167
2559
|
|
|
2168
2560
|
core.setOutput('branch_name', branchName);
|
|
2169
2561
|
|
|
2170
|
-
- name:
|
|
2562
|
+
- name: Remove container
|
|
2171
2563
|
id: destroy
|
|
2172
2564
|
uses: appleboy/ssh-action@v1.0.3
|
|
2173
2565
|
with:
|
|
@@ -2177,6 +2569,8 @@ jobs:
|
|
|
2177
2569
|
key: ${{ secrets.SSH_KEY }}
|
|
2178
2570
|
port: ${{ env.SSH_PORT }}
|
|
2179
2571
|
script: |
|
|
2572
|
+
${{ env.PAW_REMOTE }}
|
|
2573
|
+
|
|
2180
2574
|
export PATH=$PATH:/usr/local/bin
|
|
2181
2575
|
|
|
2182
2576
|
PROJECT_NAME="${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}"
|
|
@@ -2184,40 +2578,43 @@ jobs:
|
|
|
2184
2578
|
CONTAINER_NAME="${PROJECT_NAME}-${CONTAINER_SUFFIX}"
|
|
2185
2579
|
|
|
2186
2580
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
2187
|
-
|
|
2581
|
+
m wf_preview.r_destroy_head_custom
|
|
2188
2582
|
echo "ββββββββββββββββββββββββββββββββββββββββ"
|
|
2189
2583
|
|
|
2190
2584
|
if docker ps -a --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"; then
|
|
2191
|
-
|
|
2585
|
+
m wf_preview.r_container_removing_named container_name="${CONTAINER_NAME}"
|
|
2192
2586
|
docker stop ${CONTAINER_NAME} || true
|
|
2193
2587
|
docker rm ${CONTAINER_NAME} || true
|
|
2194
2588
|
echo "DESTROYED=true"
|
|
2195
2589
|
else
|
|
2196
|
-
|
|
2590
|
+
m wf_preview.r_container_missing_info container_name="${CONTAINER_NAME}"
|
|
2197
2591
|
echo "DESTROYED=false"
|
|
2198
2592
|
fi
|
|
2199
2593
|
|
|
2200
|
-
- name:
|
|
2594
|
+
- name: Destroy confirmation comment
|
|
2201
2595
|
uses: actions/github-script@v9
|
|
2202
2596
|
with:
|
|
2203
2597
|
script: |
|
|
2598
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2599
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2600
|
+
|
|
2204
2601
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
2205
2602
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
2206
2603
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
2207
2604
|
const containerName = `${projectName}-${containerSuffix}`;
|
|
2208
2605
|
|
|
2209
2606
|
const body = [
|
|
2210
|
-
'
|
|
2607
|
+
m('wf_preview.title_custom_destroyed_short'),
|
|
2211
2608
|
'',
|
|
2212
|
-
'
|
|
2609
|
+
m('wf_preview.tbl_items'),
|
|
2213
2610
|
'|------|-----|',
|
|
2214
|
-
|
|
2215
|
-
|
|
2611
|
+
m('wf_preview.env_branch', {branchName}),
|
|
2612
|
+
m('wf_preview.env_container_name', {containerName}),
|
|
2216
2613
|
'',
|
|
2217
|
-
|
|
2614
|
+
m('wf_preview.redeploy_hint_branch', {branchName}),
|
|
2218
2615
|
'',
|
|
2219
2616
|
'---',
|
|
2220
|
-
'
|
|
2617
|
+
m('wf_preview.footer_generic')
|
|
2221
2618
|
].join('\n');
|
|
2222
2619
|
|
|
2223
2620
|
await github.rest.issues.createComment({
|
|
@@ -2228,7 +2625,7 @@ jobs:
|
|
|
2228
2625
|
});
|
|
2229
2626
|
|
|
2230
2627
|
check-status-custom-branch:
|
|
2231
|
-
name: Preview
|
|
2628
|
+
name: Preview status (Custom Branch)
|
|
2232
2629
|
needs: check-command
|
|
2233
2630
|
if: |
|
|
2234
2631
|
needs.check-command.outputs.is_valid == 'true' &&
|
|
@@ -2236,7 +2633,28 @@ jobs:
|
|
|
2236
2633
|
needs.check-command.outputs.is_custom_branch == 'true'
|
|
2237
2634
|
runs-on: ubuntu-latest
|
|
2238
2635
|
steps:
|
|
2239
|
-
|
|
2636
|
+
# Messages come from the same revision as this workflow (the PR merge commit on pull_request, otherwise the
|
|
2637
|
+
# default branch) so they exist even before the default branch has them, and before the PR branch is checked out
|
|
2638
|
+
- name: Checkout messages
|
|
2639
|
+
uses: actions/checkout@v7
|
|
2640
|
+
with:
|
|
2641
|
+
ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
|
|
2642
|
+
sparse-checkout: |
|
|
2643
|
+
.github/scripts/messages.py
|
|
2644
|
+
version.yml
|
|
2645
|
+
sparse-checkout-cone-mode: false
|
|
2646
|
+
|
|
2647
|
+
- name: Load messages
|
|
2648
|
+
run: |
|
|
2649
|
+
# Keep a copy outside the workspace: checking out the PR branch later replaces its files
|
|
2650
|
+
mkdir -p "$RUNNER_TEMP/paw"
|
|
2651
|
+
cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
|
|
2652
|
+
{
|
|
2653
|
+
echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
|
|
2654
|
+
echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
|
|
2655
|
+
} >> "$GITHUB_ENV"
|
|
2656
|
+
|
|
2657
|
+
- name: Extract container suffix from branch name
|
|
2240
2658
|
id: extract
|
|
2241
2659
|
uses: actions/github-script@v9
|
|
2242
2660
|
with:
|
|
@@ -2259,7 +2677,7 @@ jobs:
|
|
|
2259
2677
|
|
|
2260
2678
|
core.setOutput('branch_name', branchName);
|
|
2261
2679
|
|
|
2262
|
-
- name:
|
|
2680
|
+
- name: Check container status
|
|
2263
2681
|
id: check_running
|
|
2264
2682
|
uses: appleboy/ssh-action@v1.0.3
|
|
2265
2683
|
continue-on-error: true
|
|
@@ -2278,10 +2696,13 @@ jobs:
|
|
|
2278
2696
|
|
|
2279
2697
|
docker ps --filter "name=${CONTAINER_NAME}" --format "{{.Names}}" | grep -q "${CONTAINER_NAME}"
|
|
2280
2698
|
|
|
2281
|
-
- name:
|
|
2699
|
+
- name: Post status comment
|
|
2282
2700
|
uses: actions/github-script@v9
|
|
2283
2701
|
with:
|
|
2284
2702
|
script: |
|
|
2703
|
+
const M = JSON.parse(process.env.PAW_MSG);
|
|
2704
|
+
const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
|
|
2705
|
+
|
|
2285
2706
|
const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}';
|
|
2286
2707
|
const containerSuffix = '${{ steps.extract.outputs.container_suffix }}';
|
|
2287
2708
|
const branchName = '${{ steps.extract.outputs.branch_name }}';
|
|
@@ -2295,38 +2716,38 @@ jobs:
|
|
|
2295
2716
|
let body;
|
|
2296
2717
|
if (isRunning) {
|
|
2297
2718
|
body = [
|
|
2298
|
-
'
|
|
2719
|
+
m('wf_preview.title_custom_running'),
|
|
2299
2720
|
'',
|
|
2300
|
-
'
|
|
2721
|
+
m('wf_preview.tbl_items'),
|
|
2301
2722
|
'|------|-----|',
|
|
2302
|
-
|
|
2723
|
+
m('wf_preview.env_branch', {branchName}),
|
|
2303
2724
|
`| **Preview URL** | ${previewUrl} |`,
|
|
2304
|
-
|
|
2305
|
-
'
|
|
2725
|
+
m('wf_preview.env_container_name', {containerName}),
|
|
2726
|
+
m('wf_preview.env_status_running'),
|
|
2306
2727
|
'',
|
|
2307
|
-
'
|
|
2308
|
-
'
|
|
2728
|
+
m('wf_preview.sec_commands'),
|
|
2729
|
+
m('wf_preview.tbl_commands'),
|
|
2309
2730
|
'|--------|------|',
|
|
2310
|
-
|
|
2311
|
-
|
|
2731
|
+
m('wf_preview.cmd_build_branch', {branchName}),
|
|
2732
|
+
m('wf_preview.cmd_destroy_branch', {branchName}),
|
|
2312
2733
|
'',
|
|
2313
2734
|
'---',
|
|
2314
|
-
'
|
|
2735
|
+
m('wf_preview.footer_generic')
|
|
2315
2736
|
].join('\n');
|
|
2316
2737
|
} else {
|
|
2317
2738
|
body = [
|
|
2318
|
-
'
|
|
2739
|
+
m('wf_preview.title_custom_absent'),
|
|
2319
2740
|
'',
|
|
2320
|
-
'
|
|
2741
|
+
m('wf_preview.tbl_items'),
|
|
2321
2742
|
'|------|-----|',
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
'
|
|
2743
|
+
m('wf_preview.env_branch', {branchName}),
|
|
2744
|
+
m('wf_preview.env_container_name', {containerName}),
|
|
2745
|
+
m('wf_preview.env_status_absent'),
|
|
2325
2746
|
'',
|
|
2326
|
-
|
|
2747
|
+
m('wf_preview.deploy_hint_branch', {branchName}),
|
|
2327
2748
|
'',
|
|
2328
2749
|
'---',
|
|
2329
|
-
'
|
|
2750
|
+
m('wf_preview.footer_generic')
|
|
2330
2751
|
].join('\n');
|
|
2331
2752
|
}
|
|
2332
2753
|
|