mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-27 11:14:59 +00:00
feat(ci): add PR batch test deployment workflow (#275)
* feat(ci): add PR batch test deployment workflow * fix(ci): support local PR batch rehearsal --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
0497f8654f
commit
689e698b89
5 changed files with 679 additions and 0 deletions
161
.github/workflows/pr-batch-test-deploy.yml
vendored
Normal file
161
.github/workflows/pr-batch-test-deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
name: PR Batch Test Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_numbers:
|
||||
description: "Comma/newline separated PR numbers to merge onto the base branch"
|
||||
required: true
|
||||
type: string
|
||||
base_ref:
|
||||
description: "Base branch to build from"
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
deploy_channel:
|
||||
description: "Floating image tag used by the shared HK test machine"
|
||||
required: false
|
||||
default: manual-test-hk
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: pr-batch-test-runtime
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
DOCKER_PLATFORM: linux/amd64
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
name: Build And Deploy Manual Test Batch
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Ensure helper scripts are executable
|
||||
run: chmod +x scripts/prepare-pr-batch.sh scripts/deploy-test-runtime.sh
|
||||
|
||||
- name: Validate deploy secrets
|
||||
env:
|
||||
TEST_RUNTIME_SSH_HOST: ${{ secrets.TEST_RUNTIME_SSH_HOST }}
|
||||
TEST_RUNTIME_SSH_KEY: ${{ secrets.TEST_RUNTIME_SSH_KEY }}
|
||||
run: |
|
||||
[[ -n "${TEST_RUNTIME_SSH_HOST}" ]] || { echo "::error::Missing secret TEST_RUNTIME_SSH_HOST"; exit 1; }
|
||||
[[ -n "${TEST_RUNTIME_SSH_KEY}" ]] || { echo "::error::Missing secret TEST_RUNTIME_SSH_KEY"; exit 1; }
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Merge selected PRs onto base ref
|
||||
id: batch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
scripts/prepare-pr-batch.sh \
|
||||
--pr-list "${{ inputs.pr_numbers }}" \
|
||||
--base-ref "${{ inputs.base_ref }}" \
|
||||
--deploy-channel "${{ inputs.deploy_channel }}"
|
||||
|
||||
- name: Build and push backend image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./server
|
||||
file: ./server/Dockerfile
|
||||
platforms: ${{ env.DOCKER_PLATFORM }}
|
||||
push: true
|
||||
provenance: false
|
||||
sbom: false
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/skillhub-server:${{ steps.batch.outputs.deploy_tag }}
|
||||
ghcr.io/${{ github.repository_owner }}/skillhub-server:${{ steps.batch.outputs.immutable_tag }}
|
||||
cache-from: type=gha,scope=manual-test-server
|
||||
cache-to: type=gha,mode=max,scope=manual-test-server
|
||||
|
||||
- name: Build and push frontend image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./web
|
||||
file: ./web/Dockerfile
|
||||
platforms: ${{ env.DOCKER_PLATFORM }}
|
||||
push: true
|
||||
provenance: false
|
||||
sbom: false
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/skillhub-web:${{ steps.batch.outputs.deploy_tag }}
|
||||
ghcr.io/${{ github.repository_owner }}/skillhub-web:${{ steps.batch.outputs.immutable_tag }}
|
||||
cache-from: type=gha,scope=manual-test-web
|
||||
cache-to: type=gha,mode=max,scope=manual-test-web
|
||||
|
||||
- name: Build and push scanner image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./scanner
|
||||
file: ./scanner/Dockerfile
|
||||
platforms: ${{ env.DOCKER_PLATFORM }}
|
||||
push: true
|
||||
provenance: false
|
||||
sbom: false
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/skillhub-scanner:${{ steps.batch.outputs.deploy_tag }}
|
||||
ghcr.io/${{ github.repository_owner }}/skillhub-scanner:${{ steps.batch.outputs.immutable_tag }}
|
||||
cache-from: type=gha,scope=manual-test-scanner
|
||||
cache-to: type=gha,mode=max,scope=manual-test-scanner
|
||||
|
||||
- name: Prepare deploy key
|
||||
id: ssh
|
||||
env:
|
||||
TEST_RUNTIME_SSH_KEY: ${{ secrets.TEST_RUNTIME_SSH_KEY }}
|
||||
run: |
|
||||
key_file="${RUNNER_TEMP}/test-runtime.key"
|
||||
printf '%s\n' "${TEST_RUNTIME_SSH_KEY}" > "${key_file}"
|
||||
chmod 600 "${key_file}"
|
||||
echo "key_file=${key_file}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Deploy batch images to HK test runtime
|
||||
env:
|
||||
TEST_RUNTIME_SSH_HOST: ${{ secrets.TEST_RUNTIME_SSH_HOST }}
|
||||
TEST_RUNTIME_SSH_USER: ${{ secrets.TEST_RUNTIME_SSH_USER }}
|
||||
TEST_RUNTIME_SSH_PORT: ${{ secrets.TEST_RUNTIME_SSH_PORT }}
|
||||
run: |
|
||||
ssh_port="${TEST_RUNTIME_SSH_PORT:-22}"
|
||||
ssh_user="${TEST_RUNTIME_SSH_USER:-skillhub-deploy}"
|
||||
scripts/deploy-test-runtime.sh \
|
||||
--host "${TEST_RUNTIME_SSH_HOST}" \
|
||||
--user "${ssh_user}" \
|
||||
--port "${ssh_port}" \
|
||||
--key-file "${{ steps.ssh.outputs.key_file }}" \
|
||||
--deploy-tag "${{ steps.batch.outputs.deploy_tag }}" \
|
||||
--immutable-tag "${{ steps.batch.outputs.immutable_tag }}" \
|
||||
--merged-sha "${{ steps.batch.outputs.merged_sha }}" \
|
||||
--pr-csv "${{ steps.batch.outputs.pr_csv }}" \
|
||||
--run-url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
- name: Publish final summary
|
||||
run: |
|
||||
{
|
||||
echo "### HK manual test runtime updated"
|
||||
echo
|
||||
echo "- URL: \`https://skill.xf-yun.com.cn\`"
|
||||
echo "- Base ref: \`${{ steps.batch.outputs.base_ref }}\`"
|
||||
echo "- Floating tag: \`${{ steps.batch.outputs.deploy_tag }}\`"
|
||||
echo "- Immutable tag: \`${{ steps.batch.outputs.immutable_tag }}\`"
|
||||
echo "- Merged SHA: \`${{ steps.batch.outputs.merged_sha }}\`"
|
||||
echo "- PR list: \`${{ steps.batch.outputs.pr_csv }}\`"
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
81
docs/pr-batch-test-runtime.md
Normal file
81
docs/pr-batch-test-runtime.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# PR Batch Test Runtime
|
||||
|
||||
This repository includes a manual GitHub Actions workflow that builds a
|
||||
synthetic test image set from multiple PRs and deploys it to the shared
|
||||
Hong Kong manual-test machine.
|
||||
|
||||
Workflow file:
|
||||
|
||||
- `.github/workflows/pr-batch-test-deploy.yml`
|
||||
|
||||
## What the workflow does
|
||||
|
||||
When you trigger the workflow manually, it:
|
||||
|
||||
1. checks out the repository and fetches the selected base branch
|
||||
2. parses the PR list you provide and deduplicates it while preserving order
|
||||
3. verifies that every PR is still open and targets the chosen base branch
|
||||
4. merges the selected PR heads onto the base branch in the exact order you supplied
|
||||
5. fails fast if any PR conflicts with the base branch or with an earlier PR in the batch
|
||||
6. builds `server`, `web`, and `scanner` images for `linux/amd64`
|
||||
7. pushes both a floating tag and an immutable tag to GHCR
|
||||
8. SSHes into the HK test machine as a dedicated deploy user
|
||||
9. calls a root-owned deployment wrapper through `sudo`
|
||||
10. updates `/opt/skillhub-runtime/.env.release` and runs `docker compose pull && docker compose up -d`
|
||||
|
||||
The floating tag is the shared environment channel. By default it is
|
||||
`manual-test-hk`. Each run also pushes an immutable tag for traceability:
|
||||
|
||||
- floating tag example: `manual-test-hk`
|
||||
- immutable tag example: `manual-test-hk-128-3d4a8e7f9a1b`
|
||||
|
||||
The runtime always deploys the floating tag, so the same test URL keeps
|
||||
working while still letting maintainers look up the exact image version
|
||||
used by a given run.
|
||||
|
||||
## Required GitHub secrets
|
||||
|
||||
Add these repository or environment secrets before using the workflow:
|
||||
|
||||
- `TEST_RUNTIME_SSH_HOST`: test machine hostname or IP
|
||||
- `TEST_RUNTIME_SSH_KEY`: private key content used by GitHub Actions
|
||||
|
||||
Optional secrets:
|
||||
|
||||
- `TEST_RUNTIME_SSH_USER`: defaults to `skillhub-deploy`
|
||||
- `TEST_RUNTIME_SSH_PORT`: defaults to `22`
|
||||
|
||||
The remote machine should expose a root-owned deployment command at:
|
||||
|
||||
- `/usr/local/bin/skillhub-test-deploy`
|
||||
|
||||
The dedicated deploy user is expected to have passwordless sudo access to
|
||||
that command only.
|
||||
|
||||
## Recommended usage
|
||||
|
||||
Open the workflow in GitHub Actions and fill in:
|
||||
|
||||
- `pr_numbers`: a comma-separated or newline-separated list such as `123, 124, 130`
|
||||
- `base_ref`: usually `main`
|
||||
- `deploy_channel`: keep the default `manual-test-hk` for the shared test machine
|
||||
|
||||
The merge order matters. If PR `124` depends on `123`, list `123` first.
|
||||
|
||||
## Runtime metadata on the server
|
||||
|
||||
After deployment, the workflow writes a small metadata file here:
|
||||
|
||||
- `/opt/skillhub-runtime/manual-test-deployment.txt`
|
||||
|
||||
It records:
|
||||
|
||||
- deploy time
|
||||
- floating tag
|
||||
- immutable tag
|
||||
- merged synthetic SHA
|
||||
- PR list
|
||||
- GitHub Actions run URL
|
||||
|
||||
This makes it easy for testers and maintainers to confirm which batch is
|
||||
currently deployed.
|
||||
128
scripts/deploy-test-runtime.sh
Executable file
128
scripts/deploy-test-runtime.sh
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/deploy-test-runtime.sh [options]
|
||||
|
||||
Options:
|
||||
--host <host> Remote SSH host
|
||||
--user <user> Remote SSH user. Default: skillhub-deploy
|
||||
--port <port> Remote SSH port. Default: 22
|
||||
--key-file <path> SSH private key for deployment
|
||||
--deploy-tag <tag> Floating image tag to deploy
|
||||
--immutable-tag <tag> Immutable image tag for traceability
|
||||
--merged-sha <sha> Synthetic merge commit SHA
|
||||
--pr-csv <list> Comma-separated PR numbers
|
||||
--run-url <url> GitHub Actions run URL
|
||||
EOF
|
||||
}
|
||||
|
||||
ssh_host=""
|
||||
ssh_user="skillhub-deploy"
|
||||
ssh_port="22"
|
||||
ssh_key_file=""
|
||||
deploy_tag=""
|
||||
immutable_tag=""
|
||||
merged_sha=""
|
||||
pr_csv=""
|
||||
run_url=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--host)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --host" >&2; exit 1; }
|
||||
ssh_host="$2"
|
||||
shift 2
|
||||
;;
|
||||
--user)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --user" >&2; exit 1; }
|
||||
ssh_user="$2"
|
||||
shift 2
|
||||
;;
|
||||
--port)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --port" >&2; exit 1; }
|
||||
ssh_port="$2"
|
||||
shift 2
|
||||
;;
|
||||
--key-file)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --key-file" >&2; exit 1; }
|
||||
ssh_key_file="$2"
|
||||
shift 2
|
||||
;;
|
||||
--deploy-tag)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --deploy-tag" >&2; exit 1; }
|
||||
deploy_tag="$2"
|
||||
shift 2
|
||||
;;
|
||||
--immutable-tag)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --immutable-tag" >&2; exit 1; }
|
||||
immutable_tag="$2"
|
||||
shift 2
|
||||
;;
|
||||
--merged-sha)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --merged-sha" >&2; exit 1; }
|
||||
merged_sha="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pr-csv)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --pr-csv" >&2; exit 1; }
|
||||
pr_csv="$2"
|
||||
shift 2
|
||||
;;
|
||||
--run-url)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --run-url" >&2; exit 1; }
|
||||
run_url="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "${ssh_host}" ]] || { echo "--host is required" >&2; exit 1; }
|
||||
[[ -n "${ssh_key_file}" ]] || { echo "--key-file is required" >&2; exit 1; }
|
||||
[[ -n "${deploy_tag}" ]] || { echo "--deploy-tag is required" >&2; exit 1; }
|
||||
[[ -n "${immutable_tag}" ]] || { echo "--immutable-tag is required" >&2; exit 1; }
|
||||
|
||||
ssh_opts=(
|
||||
-i "${ssh_key_file}"
|
||||
-o BatchMode=yes
|
||||
-o IdentitiesOnly=yes
|
||||
-o StrictHostKeyChecking=accept-new
|
||||
-o ServerAliveInterval=15
|
||||
-o ServerAliveCountMax=3
|
||||
-o TCPKeepAlive=yes
|
||||
-o ConnectTimeout=10
|
||||
-p "${ssh_port}"
|
||||
)
|
||||
|
||||
ssh "${ssh_opts[@]}" "${ssh_user}@${ssh_host}" bash -s -- \
|
||||
"${deploy_tag}" \
|
||||
"${immutable_tag}" \
|
||||
"${merged_sha}" \
|
||||
"${pr_csv}" \
|
||||
"${run_url}" <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
deploy_tag="$1"
|
||||
immutable_tag="$2"
|
||||
merged_sha="$3"
|
||||
pr_csv="$4"
|
||||
run_url="${5:-}"
|
||||
|
||||
sudo /usr/local/bin/skillhub-test-deploy \
|
||||
--deploy-tag "${deploy_tag}" \
|
||||
--immutable-tag "${immutable_tag}" \
|
||||
--merged-sha "${merged_sha}" \
|
||||
--pr-csv "${pr_csv}" \
|
||||
--run-url "${run_url}"
|
||||
EOF
|
||||
174
scripts/prepare-pr-batch.sh
Executable file
174
scripts/prepare-pr-batch.sh
Executable file
|
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/prepare-pr-batch.sh --pr-list "123,456" [options]
|
||||
|
||||
Options:
|
||||
--base-ref <ref> Base branch to merge onto. Default: main
|
||||
--deploy-channel <tag> Floating image tag for the shared test runtime.
|
||||
Default: manual-test-hk
|
||||
EOF
|
||||
}
|
||||
|
||||
base_ref="main"
|
||||
deploy_channel="manual-test-hk"
|
||||
pr_input=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--base-ref)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --base-ref" >&2; exit 1; }
|
||||
base_ref="$2"
|
||||
shift 2
|
||||
;;
|
||||
--deploy-channel)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --deploy-channel" >&2; exit 1; }
|
||||
deploy_channel="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pr-list)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --pr-list" >&2; exit 1; }
|
||||
pr_input="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
: "${GH_TOKEN:?GH_TOKEN is required}"
|
||||
|
||||
if [[ -z "${pr_input}" ]]; then
|
||||
echo "--pr-list is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
normalized_input="$(printf '%s' "${pr_input}" | tr ',;\r\n\t' ' ')"
|
||||
|
||||
declare -a pr_numbers=()
|
||||
|
||||
for token in ${normalized_input}; do
|
||||
if [[ ! "${token}" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid PR number: ${token}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
already_seen=false
|
||||
if [[ "${#pr_numbers[@]}" -gt 0 ]]; then
|
||||
for existing in "${pr_numbers[@]}"; do
|
||||
if [[ "${existing}" == "${token}" ]]; then
|
||||
already_seen=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "${already_seen}" == "true" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
pr_numbers+=("${token}")
|
||||
done
|
||||
|
||||
if [[ "${#pr_numbers[@]}" -eq 0 ]]; then
|
||||
echo "No PR numbers were parsed from --pr-list" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sanitized_channel="$(
|
||||
printf '%s' "${deploy_channel}" |
|
||||
tr '[:upper:]' '[:lower:]' |
|
||||
sed -E 's/[^a-z0-9._-]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g'
|
||||
)"
|
||||
|
||||
if [[ -z "${sanitized_channel}" ]]; then
|
||||
echo "Deploy channel resolved to an empty tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git fetch --no-tags origin "${base_ref}"
|
||||
git checkout -B manual-test-batch "origin/${base_ref}"
|
||||
|
||||
summary_file="${RUNNER_TEMP:-/tmp}/manual-test-batch-summary.md"
|
||||
current_pr=""
|
||||
trap 'status=$?; if [[ $status -ne 0 && -n "${current_pr}" ]]; then echo "Failed while merging PR #${current_pr}" >&2; fi' EXIT
|
||||
|
||||
{
|
||||
echo "### Manual Test Batch"
|
||||
echo
|
||||
echo "- Base ref: \`${base_ref}\`"
|
||||
echo "- Deploy channel: \`${sanitized_channel}\`"
|
||||
echo "- Selected PRs:"
|
||||
} > "${summary_file}"
|
||||
|
||||
for pr in "${pr_numbers[@]}"; do
|
||||
current_pr="${pr}"
|
||||
|
||||
IFS=$'\t' read -r state pr_base is_draft title url <<EOF
|
||||
$(gh pr view "${pr}" \
|
||||
--json state,baseRefName,isDraft,title,url \
|
||||
--jq '[.state, .baseRefName, (.isDraft|tostring), .title, .url] | @tsv')
|
||||
EOF
|
||||
|
||||
if [[ "${state}" != "OPEN" ]]; then
|
||||
echo "PR #${pr} is not open (state=${state})" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${pr_base}" != "${base_ref}" ]]; then
|
||||
echo "PR #${pr} targets ${pr_base}, expected ${base_ref}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --no-tags origin "pull/${pr}/head:refs/remotes/origin/manual-test-pr-${pr}"
|
||||
git merge --no-ff --no-edit \
|
||||
-m "Merge PR #${pr} for manual test batch" \
|
||||
"refs/remotes/origin/manual-test-pr-${pr}"
|
||||
|
||||
if [[ "${is_draft}" == "true" ]]; then
|
||||
title="${title} [draft]"
|
||||
fi
|
||||
|
||||
echo " - #${pr} ${title} (${url})" >> "${summary_file}"
|
||||
done
|
||||
|
||||
merged_sha="$(git rev-parse HEAD)"
|
||||
short_sha="$(git rev-parse --short=12 HEAD)"
|
||||
run_token="${GITHUB_RUN_NUMBER:-manual}"
|
||||
immutable_tag="${sanitized_channel}-${run_token}-${short_sha}"
|
||||
pr_csv="$(IFS=,; echo "${pr_numbers[*]}")"
|
||||
|
||||
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
|
||||
{
|
||||
echo "base_ref=${base_ref}"
|
||||
echo "deploy_tag=${sanitized_channel}"
|
||||
echo "immutable_tag=${immutable_tag}"
|
||||
echo "merged_sha=${merged_sha}"
|
||||
echo "short_sha=${short_sha}"
|
||||
echo "pr_csv=${pr_csv}"
|
||||
echo "summary_file=${summary_file}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "- Merged SHA: \`${merged_sha}\`"
|
||||
echo "- Floating tag: \`${sanitized_channel}\`"
|
||||
echo "- Immutable tag: \`${immutable_tag}\`"
|
||||
} >> "${summary_file}"
|
||||
|
||||
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
|
||||
cat "${summary_file}" >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
135
scripts/skillhub-test-deploy-remote.sh
Normal file
135
scripts/skillhub-test-deploy-remote.sh
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: /usr/local/bin/skillhub-test-deploy [options]
|
||||
|
||||
Options:
|
||||
--deploy-tag <tag> Floating image tag to deploy
|
||||
--immutable-tag <tag> Immutable image tag for traceability
|
||||
--merged-sha <sha> Synthetic merge commit SHA
|
||||
--pr-csv <list> Comma-separated PR numbers
|
||||
--run-url <url> GitHub Actions run URL
|
||||
EOF
|
||||
}
|
||||
|
||||
runtime_dir="/opt/skillhub-runtime"
|
||||
deploy_tag=""
|
||||
immutable_tag=""
|
||||
merged_sha=""
|
||||
pr_csv=""
|
||||
run_url=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--deploy-tag)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --deploy-tag" >&2; exit 1; }
|
||||
deploy_tag="$2"
|
||||
shift 2
|
||||
;;
|
||||
--immutable-tag)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --immutable-tag" >&2; exit 1; }
|
||||
immutable_tag="$2"
|
||||
shift 2
|
||||
;;
|
||||
--merged-sha)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --merged-sha" >&2; exit 1; }
|
||||
merged_sha="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pr-csv)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --pr-csv" >&2; exit 1; }
|
||||
pr_csv="$2"
|
||||
shift 2
|
||||
;;
|
||||
--run-url)
|
||||
[[ $# -ge 2 ]] || { echo "Missing value for --run-url" >&2; exit 1; }
|
||||
run_url="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "${deploy_tag}" ]] || { echo "--deploy-tag is required" >&2; exit 1; }
|
||||
[[ -n "${immutable_tag}" ]] || { echo "--immutable-tag is required" >&2; exit 1; }
|
||||
|
||||
if [[ ! "${deploy_tag}" =~ ^[a-z0-9._-]+$ ]]; then
|
||||
echo "Invalid deploy tag: ${deploy_tag}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "${immutable_tag}" =~ ^[a-z0-9._-]+$ ]]; then
|
||||
echo "Invalid immutable tag: ${immutable_tag}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${merged_sha}" && ! "${merged_sha}" =~ ^[0-9a-f]{7,64}$ ]]; then
|
||||
echo "Invalid merged SHA: ${merged_sha}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${pr_csv}" && ! "${pr_csv}" =~ ^[0-9]+(,[0-9]+)*$ ]]; then
|
||||
echo "Invalid PR list: ${pr_csv}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${run_url}" && ! "${run_url}" =~ ^https://github\.com/.+/actions/runs/[0-9]+$ ]]; then
|
||||
echo "Invalid run URL: ${run_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set_env_value() {
|
||||
key="$1"
|
||||
value="$2"
|
||||
tmp=".env.release.tmp"
|
||||
|
||||
if grep -q "^${key}=" .env.release; then
|
||||
sed "s|^${key}=.*|${key}=${value}|" .env.release > "${tmp}"
|
||||
else
|
||||
cp .env.release "${tmp}"
|
||||
printf '%s=%s\n' "${key}" "${value}" >> "${tmp}"
|
||||
fi
|
||||
|
||||
mv "${tmp}" .env.release
|
||||
}
|
||||
|
||||
cd "${runtime_dir}"
|
||||
|
||||
test -f .env.release
|
||||
test -f compose.release.yml
|
||||
|
||||
cp .env.release ".env.release.bak.$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
set_env_value "SKILLHUB_VERSION" "${deploy_tag}"
|
||||
|
||||
cat > manual-test-deployment.txt <<METADATA
|
||||
deployed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
deploy_tag=${deploy_tag}
|
||||
immutable_tag=${immutable_tag}
|
||||
merged_sha=${merged_sha}
|
||||
pr_numbers=${pr_csv}
|
||||
run_url=${run_url}
|
||||
METADATA
|
||||
|
||||
docker compose --env-file .env.release -f compose.release.yml pull
|
||||
docker compose --env-file .env.release -f compose.release.yml up -d
|
||||
docker compose --env-file .env.release -f compose.release.yml ps
|
||||
|
||||
web_port="$(awk -F= '/^WEB_PORT=/{print $2}' .env.release | tail -n 1)"
|
||||
if [[ -z "${web_port}" ]]; then
|
||||
web_port="80"
|
||||
fi
|
||||
|
||||
curl -fsS http://127.0.0.1:8080/actuator/health >/dev/null
|
||||
curl -fsS "http://127.0.0.1:${web_port}/nginx-health" >/dev/null
|
||||
Loading…
Add table
Reference in a new issue