-
Notifications
You must be signed in to change notification settings - Fork 0
ci: 상용 배포용 태그 자동 생성 워크플로 추가 (Promote to Production) #241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # GitHub Actions 탭에서 버튼 한 번으로 상용 배포를 트리거하기 위한 워크플로. | ||
| # main의 다음 semver 태그(v1.2.3 형태)를 자동으로 계산해 push하고, | ||
| # 그 태그 push가 prod-cd.yml의 트리거(on.push.tags)를 실행시켜 상용 배포로 이어진다. | ||
| name: Promote to Production | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| bump: | ||
| description: "버전 증가 단위" | ||
| required: true | ||
| default: "patch" | ||
| type: choice | ||
| options: | ||
| - patch | ||
| - minor | ||
| - major | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| tag: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| with: | ||
| fetch-depth: 0 | ||
|
Comment on lines
+26
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/promote-to-prod.yml
printf '%s\n' '--- repository action references and runner versions ---'
rg -n --glob '*.yml' --glob '*.yaml' 'actions/checkout@|runs-on:|actionlint' .
printf '%s\n' '--- available actionlint ---'
if command -v actionlint >/dev/null 2>&1; then actionlint -version; else echo 'actionlint not installed'; fiRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 2640 🌐 Web query:
💡 Result: The actions/checkout version 6.0.2 uses the Node.js 24 runtime [1][2]. To support this runtime, a minimum GitHub Actions Runner version of v2.327.1 or later is required [1][2]. Additionally, if you use the persist-credentials feature in workflows involving Docker container actions, a minimum runner version of v2.329.0 or later is required to properly access the credentials stored in the runner's temporary directory [3][1][2]. Citations:
🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- checkout releases ---'
curl -fsSL https://api.github.com/repos/actions/checkout/releases/latest |
jq '{tag_name, name, published_at, html_url}'
printf '%s\n' '--- checkout v3 and v6 metadata ---'
for tag in v3 v6; do
printf '%s\n' "[$tag]"
curl -fsSL "https://api.github.com/repos/actions/checkout/git/ref/tags/$tag" |
jq '{ref, object}'
done
printf '%s\n' '--- repository policy files mentioning action pinning ---'
rg -n -i 'pin(ned|ning)?|commit sha|full[- ]length sha|actions/checkout|github actions' \
.github README.md CONTRIBUTING.md SECURITY.md 2>/dev/null || true
printf '%s\n' '--- actionlint availability ---'
if command -v actionlint >/dev/null 2>&1; then
actionlint -version
else
echo 'actionlint not installed'
fiRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 1137 🏁 Script executed: #!/bin/bash
set +e
printf '%s\n' '--- actionlint target workflow ---'
actionlint .github/workflows/promote-to-prod.yml
printf '%s\n' '--- actionlint all workflows ---'
actionlint .github/workflows
printf '%s\n' '--- checkout tags from the upstream repository ---'
git ls-remote --tags --refs https://github.com/actions/checkout.git 'v[0-9]*' |
tail -n 20Repository: Runnect/Runnect-Spring-Boot-Server Length of output: 1778 🌐 Web query:
💡 Result: The official actions/checkout v7.0.1 action requires the GitHub Actions runner to be at version v2.327.1 or later to support the Node.js 24 runtime [1][2][3]. This requirement is explicitly stated in the action's documentation as a result of its migration to Node.js 24 [1][2]. While the action utilizes Node.js 24 for execution, the runner-level requirement ensures that the underlying infrastructure is compatible with this runtime version [4]. Citations:
Update Use 🧰 Tools🪛 actionlint (1.7.12)[error] 26-26: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue (action) 🪛 zizmor (1.29.0)[warning] 26-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) 🤖 Prompt for AI AgentsSource: Linters/SAST tools 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/promote-to-prod.yml
printf '%s\n' '--- related workflow references ---'
rg -n --hidden --glob '!node_modules' 'promote-to-prod|workflow_dispatch|actions/checkout@' .github README.md 2>/dev/null || trueRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 3222 🌐 Web query:
💡 Result: To trigger a workflow using the workflow_dispatch event, the workflow file must exist on the repository's default branch [1][2][3]. However, once triggered, you can select any branch or tag to run the workflow against via the GitHub UI, CLI, or API [1][4][5]. Key points regarding branch selection and permissions: 1. Workflow File Availability: The workflow file itself must be present in the default branch to be discoverable and triggerable [1][2]. If you want to run a version of a workflow that exists only on a feature branch, it must first be merged into the default branch [6]. 2. Selecting the Ref: - GitHub UI: When manually triggering the workflow, a dropdown menu allows you to select the desired branch or tag [1][4]. - GitHub CLI: Use the --ref flag (e.g., gh workflow run WORKFLOW --ref BRANCH) [1][4]. - API: Provide the ref parameter in the request body [5]. 3. github.ref Context: The github.ref context variable represents the branch or tag that was selected and triggered the run [7][8]. For a branch, it is returned in the format refs/heads/<branch_name>, and for a tag, it is refs/tags/<tag_name> [7][8]. 4. actions/checkout Behavior: When using the actions/checkout action, if you do not specify a ref, it defaults to the branch or tag that triggered the workflow run (the value of github.ref) [9][10]. This ensures that the code checked out matches the branch/tag you selected in the UI/CLI/API [9][10]. 5. Permissions: Users must have write access to the repository to trigger a workflow_dispatch event [1]. No additional special permissions are required for the workflow itself to access different branches, provided the user has sufficient repository access to trigger it [1]. In summary, while the workflow definition is pinned to the default branch, the execution context (code version) is determined by the ref you select at runtime, which is automatically respected by the default behavior of actions/checkout [1][9]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- production workflow ---'
cat -n .github/workflows/prod-cd.yml
printf '%s\n' '--- deployment documentation ---'
sed -n '160,195p' README.mdRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 5298 Restrict promotion to
Suggested fix jobs:
tag:
+ if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
@@
with:
fetch-depth: 0
+ ref: main🧰 Tools🪛 actionlint (1.7.12)[error] 26-26: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue (action) 🪛 zizmor (1.29.0)[warning] 26-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) 🤖 Prompt for AI Agents |
||
|
|
||
| - name: 다음 버전 태그 계산 | ||
| id: version | ||
| run: | | ||
| git fetch --tags | ||
| LATEST=$(git tag --list 'v*' --sort=-v:refname | head -n1) | ||
| if [ -z "$LATEST" ]; then | ||
| LATEST="v0.0.0" | ||
| fi | ||
| echo "현재 최신 태그: $LATEST" | ||
|
|
||
| VERSION=${LATEST#v} | ||
| MAJOR=$(echo "$VERSION" | cut -d. -f1) | ||
| MINOR=$(echo "$VERSION" | cut -d. -f2) | ||
| PATCH=$(echo "$VERSION" | cut -d. -f3) | ||
|
Comment on lines
+34
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow context ---'
sed -n '1,90p' .github/workflows/promote-to-prod.yml
printf '%s\n' '--- arithmetic assignments and shell context ---'
rg -n -C 3 'MAJOR|MINOR|PATCH|VERSION|LATEST|shell:' .github/workflows/promote-to-prod.yml
printf '%s\n' '--- standalone Bash behavior probe ---'
bash -c '
set -u
probe() {
local version=$1
local major
printf "VERSION=%q: " "$version"
MAJOR=$(echo "$version" | cut -d. -f1)
if MAJOR=$((MAJOR)); then
printf "MAJOR=%s\n" "$MAJOR"
else
printf "arithmetic_status=%s\n" "$?"
fi
}
probe "1.2.3"
probe "08.2.3"
probe "1+2.3.4"
probe "1\$(printf injected).2.3"
'Repository: Runnect/Runnect-Spring-Boot-Server Length of output: 3433 🏁 Script executed: #!/bin/bash
set +e
printf '%s\n' '--- Bash version ---'
bash --version | head -n 1
printf '%s\n' '--- arithmetic-value probes ---'
for value in \
'1+2' \
'1+$(printf COMMAND_SUBSTITUTION >&2)' \
'$(printf COMMAND_SUBSTITUTION >&2)' \
'1#$(printf COMMAND_SUBSTITUTION >&2)' \
'1;$(printf COMMAND_SUBSTITUTION >&2)' \
'1));$(printf COMMAND_SUBSTITUTION >&2);((0' \
'1[$(printf ARRAY_SUBSCRIPT >&2)]'
do
printf 'value=%q -> ' "$value"
VALUE=$value bash -c '
set +e
RESULT=$((VALUE + 1))
STATUS=$?
printf "result=%q status=%s\n" "$RESULT" "$STATUS"
' 2>&1
done
printf '%s\n' '--- candidate tag ref validation ---'
for ref in \
'refs/tags/v1+2.3.4' \
'refs/tags/v1.2.3-100' \
'refs/tags/v1$(printf injected).2.3' \
'refs/tags/v1.2.3/extra' \
'refs/tags/v01.2.3'
do
git check-ref-format "$ref" >/dev/null 2>&1
printf '%s: status=%s\n' "$ref" "$?"
doneRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 1734 Reject non-semver tags before Bash arithmetic.
🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| case "${{ inputs.bump }}" in | ||
| major) | ||
| MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 | ||
| ;; | ||
| minor) | ||
| MINOR=$((MINOR + 1)); PATCH=0 | ||
| ;; | ||
| patch) | ||
| PATCH=$((PATCH + 1)) | ||
| ;; | ||
| esac | ||
|
|
||
| NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" | ||
| echo "새 태그: $NEW_TAG" | ||
| echo "new_tag=$NEW_TAG" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: 태그 생성 및 push (→ prod-cd.yml 트리거) | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git tag "${{ steps.version.outputs.new_tag }}" | ||
| git push origin "${{ steps.version.outputs.new_tag }}" | ||
|
Comment on lines
+61
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*' 'README.md'
printf '%s\n' '--- promote-to-prod.yml ---'
cat -n .github/workflows/promote-to-prod.yml
printf '%s\n' '--- prod-cd.yml ---'
cat -n .github/workflows/prod-cd.yml
printf '%s\n' '--- references to promote/prod-cd/tag push/token ---'
rg -n -i 'promote-to-prod|prod-cd|GITHUB_TOKEN|persist-credentials|tag push|tag 생성|workflow_dispatch|on:' .github README.mdRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 8403 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*' 'README.md'
printf '%s\n' '--- promote-to-prod.yml ---'
cat -n .github/workflows/promote-to-prod.yml
printf '%s\n' '--- prod-cd.yml ---'
cat -n .github/workflows/prod-cd.yml
printf '%s\n' '--- related references ---'
rg -n -i 'promote-to-prod|prod-cd|GITHUB_TOKEN|persist-credentials|workflow_dispatch|tag 생성|tag push' .github README.mdRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 7506 🌐 Web query:
💡 Result: GitHub Actions is designed to prevent recursive workflow loops when using the default GITHUB_TOKEN. In general, events triggered by the GITHUB_TOKEN do not create new workflow runs [1][2][3]. There are a few key exceptions to this behavior: 1. Workflow Dispatch and Repository Dispatch: These events always trigger new workflow runs, even when initiated by the GITHUB_TOKEN [1][4]. 2. Pull Request Events (Opened, Synchronize, Reopened): When a workflow uses the GITHUB_TOKEN to create or update a pull request, the resulting event triggers workflow runs in an approval-required state [1][5]. These runs will not execute until a user with write access to the repository manually approves them [3][6]. Regarding the actions/checkout action, the persist-credentials input, which determines whether to configure the authentication token or SSH key in the local git configuration, has a default value of true [7][8][9]. While there have been community discussions and pull requests proposing to change this default to false to improve security, the official default remains true [7][10][11]. When set to true, the token is persisted in the local git config to enable authenticated git commands for that job, and it is removed during post-job cleanup [7][9]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
promote = Path(".github/workflows/promote-to-prod.yml").read_text()
prod_cd = Path(".github/workflows/prod-cd.yml").read_text()
checks = {
"promote workflow_dispatch": bool(re.search(r"(?m)^\s+workflow_dispatch:\s*$", promote)),
"contents write": bool(re.search(r"(?m)^\s+contents:\s*write\s*$", promote)),
"checkout persist-credentials false": bool(
re.search(r"(?m)^\s+persist-credentials:\s*false\s*$", promote)
),
"checkout token override": bool(
re.search(r"(?m)^\s+token:\s*(?!\$\{\{\s*github\.token\s*\}\})\S+", promote)
),
"git tag command": bool(re.search(r"(?m)^\s*git tag ", promote)),
"git push command": bool(re.search(r"(?m)^\s*git push ", promote)),
"prod push tag trigger": bool(re.search(r"(?m)^\s+push:\s*$", prod_cd)),
"prod v-star tag filter": bool(re.search(r"(?m)^\s+tags:\s*\[\s*['\"]v\*['\"]\s*\]", prod_cd)),
"prod workflow_dispatch": bool(re.search(r"(?m)^\s+workflow_dispatch:\s*$", prod_cd)),
"prod repository_dispatch": bool(re.search(r"(?m)^\s+repository_dispatch:\s*$", prod_cd)),
}
for name, result in checks.items():
print(f"{name}: {result}")
PYRepository: Runnect/Runnect-Spring-Boot-Server Length of output: 439 Authenticate the tag push with a non-
🧰 Tools🪛 zizmor (1.29.0)[info] 65-65: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) [info] 66-66: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: 요약 | ||
| run: echo "## ${{ steps.version.outputs.new_tag }} 태그 push 완료 — RUNNECT-PROD-CD가 곧 트리거됩니다." >> "$GITHUB_STEP_SUMMARY" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Runnect/Runnect-Spring-Boot-Server
Length of output: 2847
🏁 Script executed:
Repository: Runnect/Runnect-Spring-Boot-Server
Length of output: 415
Serialize production promotion runs.
Two manual runs can calculate the same next tag. The second
git pushthen fails because the tag already exists. Add a workflow-level concurrency group withcancel-in-progress: false.🤖 Prompt for AI Agents