332 lines
14 KiB
YAML
332 lines
14 KiB
YAML
name: Publish Container Images
|
||
|
||
on:
|
||
workflow_run:
|
||
workflows:
|
||
- Publish Supported Platform Target
|
||
types:
|
||
- completed
|
||
workflow_dispatch:
|
||
inputs:
|
||
source_commit:
|
||
description: Source commit to build
|
||
required: true
|
||
default: main
|
||
type: string
|
||
channel_commit:
|
||
description: release-channel ref to search for the matching signed target
|
||
required: false
|
||
default: release-channel
|
||
type: string
|
||
|
||
concurrency:
|
||
group: production-container-images
|
||
cancel-in-progress: false
|
||
|
||
env:
|
||
DOCKERHUB_IMAGE: szcq/deepseek-harness
|
||
GHCR_IMAGE: ghcr.io/${{ github.repository }}
|
||
|
||
jobs:
|
||
build:
|
||
name: Verify, test, and publish images
|
||
if: >-
|
||
(github.event_name == 'workflow_run' &&
|
||
github.event.workflow_run.conclusion == 'success' &&
|
||
github.event.workflow_run.head_branch == 'main') ||
|
||
github.event_name == 'workflow_dispatch'
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 90
|
||
environment: production-image
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
steps:
|
||
- name: Checkout reviewed source
|
||
uses: actions/checkout@v6
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || inputs.source_commit }}
|
||
path: source
|
||
|
||
- name: Checkout signed channel history
|
||
uses: actions/checkout@v6
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.channel_commit || 'release-channel' }}
|
||
fetch-depth: 0
|
||
path: channel
|
||
|
||
- name: Resolve immutable signed channel state
|
||
id: channel
|
||
run: |
|
||
source_commit="$(git -C source rev-parse HEAD)"
|
||
channel_commit=
|
||
while IFS= read -r candidate; do
|
||
candidate_source="$(git -C channel show "${candidate}:channel.json" | jq -er .sourceCommit)"
|
||
if test "$candidate_source" = "$source_commit"; then
|
||
channel_commit="$candidate"
|
||
break
|
||
fi
|
||
done < <(git -C channel log --format=%H -- channel.json)
|
||
if test -z "$channel_commit"; then
|
||
echo "No signed release-channel target matches source commit $source_commit" >&2
|
||
exit 1
|
||
fi
|
||
git -C channel checkout --detach "$channel_commit"
|
||
{
|
||
printf 'commit=%s\n' "$channel_commit"
|
||
printf 'source_commit=%s\n' "$source_commit"
|
||
} >> "$GITHUB_OUTPUT"
|
||
# shellcheck disable=SC2016
|
||
printf 'Using signed channel commit `%s` for source commit `%s`.\n' \
|
||
"$channel_commit" "$source_commit" >> "$GITHUB_STEP_SUMMARY"
|
||
|
||
- name: Set up Node.js
|
||
uses: actions/setup-node@v6
|
||
with:
|
||
node-version: 24
|
||
cache: npm
|
||
cache-dependency-path: |
|
||
source/container/control-plane/services/management/package-lock.json
|
||
source/container/environment/resources/plugins/platform-management/package/package-lock.json
|
||
|
||
- name: Install Management runtime dependencies
|
||
working-directory: source
|
||
run: |
|
||
npm ci --omit=dev --ignore-scripts --no-audit --no-fund --prefix container/control-plane/services/management
|
||
npm ci --omit=dev --ignore-scripts --no-audit --no-fund --legacy-peer-deps --prefix container/environment/resources/plugins/platform-management/package
|
||
|
||
- name: Verify gateway and Compose configuration
|
||
working-directory: source
|
||
run: |
|
||
npm test --prefix container/control-plane/services/gateway
|
||
npm test --prefix container/platform
|
||
node container/test/compose-config.mjs
|
||
sh -n container/test/container-smoke.sh
|
||
sh -n container/test/devtools-smoke.sh
|
||
|
||
- name: Verify signed Supported Target
|
||
id: signed-target
|
||
working-directory: source
|
||
env:
|
||
RECOVERY_ROOT_PUBLIC_KEY: ${{ secrets.DSH_RECOVERY_ROOT_PUBLIC_KEY }}
|
||
run: |
|
||
test -n "$RECOVERY_ROOT_PUBLIC_KEY"
|
||
source_commit="$(git rev-parse HEAD)"
|
||
test "$(jq -er .sourceCommit ../channel/channel.json)" = "$source_commit"
|
||
sequence="$(jq -er .targetSequence ../channel/stable.json)"
|
||
test "$(jq -er .targetSequence ../channel/channel.json)" = "$sequence"
|
||
release_dir="../channel/targets/$sequence"
|
||
test -d "$release_dir"
|
||
for name in stable.json stable.sig.json keyring.json keyring.sig.json; do
|
||
cmp "../channel/$name" "$release_dir/$name"
|
||
done
|
||
image_input=container/platform/image-input
|
||
mkdir -p "$image_input/release"
|
||
cp -a "$release_dir/." "$image_input/release/"
|
||
printf '%s\n' "$RECOVERY_ROOT_PUBLIC_KEY" > "$image_input/recovery-root.spki.base64"
|
||
cp release/supported-target.json "$image_input/supported-target.json"
|
||
cp container/environment/definition.json "$image_input/environment-definition.json"
|
||
version="$(jq -er '.latestSupportedDsh' release/supported-target.json)"
|
||
npm_input="$RUNNER_TEMP/image-npm-input"
|
||
mkdir -p "$npm_input"
|
||
npm pack "@deepseek-ai/dsh@${version}" --pack-destination "$npm_input" >/dev/null
|
||
tarball="$(find "$npm_input" -maxdepth 1 -type f -name '*.tgz' -print -quit)"
|
||
test -n "$tarball"
|
||
cp "$tarball" "$image_input/dsh.tgz"
|
||
verified="$(node container/platform/tools/verify-image-release.mjs \
|
||
"$image_input/release" "$image_input/recovery-root.spki.base64" "$image_input/dsh.tgz" \
|
||
"$image_input/supported-target.json" "$image_input/environment-definition.json")"
|
||
test "$(jq -er .dshVersion <<<"$verified")" = "$version"
|
||
test "$(jq -er .targetSequence <<<"$verified")" = "$sequence"
|
||
environment_version="$(jq -er .environmentVersion <<<"$verified")"
|
||
test "$environment_version" = "$(jq -er .version container/environment/definition.json)"
|
||
{
|
||
printf 'version=%s\n' "$version"
|
||
printf 'environment_version=%s\n' "$environment_version"
|
||
printf 'sequence=%s\n' "$sequence"
|
||
} >> "$GITHUB_OUTPUT"
|
||
cp "$image_input/recovery-root.spki.base64" container/platform/seed/trust/recovery-root.spki.base64
|
||
cp "$release_dir/keyring.json" container/platform/seed/trust/keyring.json
|
||
cp "$release_dir/keyring.sig.json" container/platform/seed/trust/keyring.sig.json
|
||
rm container/platform/seed/trust/DEVELOPMENT_FIXTURE
|
||
test ! -e container/platform/seed/trust/DEVELOPMENT_FIXTURE
|
||
printf 'Building signed DSH %s with Environment %s (sequence %s).\n' \
|
||
"$version" "$environment_version" "$sequence" >> "$GITHUB_STEP_SUMMARY"
|
||
|
||
- name: Generate registry tags
|
||
id: tags
|
||
env:
|
||
DSH_VERSION: ${{ steps.signed-target.outputs.version }}
|
||
ENVIRONMENT_VERSION: ${{ steps.signed-target.outputs.environment_version }}
|
||
run: |
|
||
[[ "$ENVIRONMENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]
|
||
major="${BASH_REMATCH[1]}"
|
||
minor="${BASH_REMATCH[2]}"
|
||
{
|
||
echo 'standard<<EOF'
|
||
echo "${DOCKERHUB_IMAGE}:${DSH_VERSION}"
|
||
echo "${GHCR_IMAGE}:latest"
|
||
echo "${GHCR_IMAGE}:${ENVIRONMENT_VERSION}"
|
||
echo "${GHCR_IMAGE}:${major}.${minor}"
|
||
echo "${GHCR_IMAGE}:${major}"
|
||
echo "${GHCR_IMAGE}:dsh-${DSH_VERSION}"
|
||
echo 'EOF'
|
||
echo 'devtools<<EOF'
|
||
echo "${DOCKERHUB_IMAGE}:${DSH_VERSION}-devtools"
|
||
echo 'EOF'
|
||
} >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Set up QEMU
|
||
uses: docker/setup-qemu-action@v4
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
|
||
- name: Build standard smoke-test image
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: ./source
|
||
file: ./source/Dockerfile
|
||
platforms: linux/amd64
|
||
load: true
|
||
provenance: false
|
||
build-args: |
|
||
SIGNED_IMAGE_INPUT=true
|
||
DSH_VERSION=${{ steps.signed-target.outputs.version }}
|
||
ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }}
|
||
TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }}
|
||
PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }}
|
||
tags: dsh-gateway-smoke:${{ github.run_id }}
|
||
cache-from: type=gha,scope=standard
|
||
cache-to: type=gha,mode=max,scope=standard
|
||
|
||
- name: Run standard container smoke checks
|
||
working-directory: source
|
||
run: container/test/container-smoke.sh "dsh-gateway-smoke:${{ github.run_id }}"
|
||
|
||
- name: Build devtools smoke-test image
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: ./source
|
||
file: ./source/Dockerfile
|
||
platforms: linux/amd64
|
||
load: true
|
||
provenance: false
|
||
build-args: |
|
||
SIGNED_IMAGE_INPUT=true
|
||
DSH_VERSION=${{ steps.signed-target.outputs.version }}
|
||
ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }}
|
||
TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }}
|
||
PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }}
|
||
INSTALL_DEVTOOLS=true
|
||
tags: dsh-gateway-devtools-smoke:${{ github.run_id }}
|
||
cache-from: type=gha,scope=devtools
|
||
cache-to: type=gha,mode=max,scope=devtools
|
||
|
||
- name: Run devtools smoke checks
|
||
working-directory: source
|
||
run: container/test/devtools-smoke.sh "dsh-gateway-devtools-smoke:${{ github.run_id }}"
|
||
|
||
- name: Log in to Docker Hub
|
||
uses: docker/login-action@v4
|
||
with:
|
||
username: szcq
|
||
password: ${{ secrets.DOCKER_TOKEN }}
|
||
|
||
- name: Log in to GitHub Container Registry
|
||
uses: docker/login-action@v4
|
||
with:
|
||
registry: ghcr.io
|
||
username: ${{ github.actor }}
|
||
password: ${{ github.token }}
|
||
|
||
- name: Push versioned devtools image to Docker Hub
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: ./source
|
||
file: ./source/Dockerfile
|
||
platforms: linux/amd64,linux/arm64
|
||
push: true
|
||
provenance: false
|
||
build-args: |
|
||
SIGNED_IMAGE_INPUT=true
|
||
DSH_VERSION=${{ steps.signed-target.outputs.version }}
|
||
ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }}
|
||
TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }}
|
||
PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }}
|
||
INSTALL_DEVTOOLS=true
|
||
tags: ${{ steps.tags.outputs.devtools }}
|
||
cache-from: type=gha,scope=devtools
|
||
cache-to: type=gha,mode=max,scope=devtools
|
||
|
||
- name: Push versioned standard image to Docker Hub and GHCR
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: ./source
|
||
file: ./source/Dockerfile
|
||
platforms: linux/amd64,linux/arm64
|
||
push: true
|
||
provenance: false
|
||
build-args: |
|
||
SIGNED_IMAGE_INPUT=true
|
||
DSH_VERSION=${{ steps.signed-target.outputs.version }}
|
||
ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }}
|
||
TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }}
|
||
PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }}
|
||
tags: ${{ steps.tags.outputs.standard }}
|
||
cache-from: type=gha,scope=standard
|
||
cache-to: type=gha,mode=max,scope=standard
|
||
|
||
- name: Promote Docker Hub rolling tags
|
||
env:
|
||
DSH_VERSION: ${{ steps.signed-target.outputs.version }}
|
||
run: |
|
||
docker buildx imagetools create \
|
||
--tag "${DOCKERHUB_IMAGE}:latest-devtools" \
|
||
"${DOCKERHUB_IMAGE}:${DSH_VERSION}-devtools"
|
||
docker buildx imagetools create \
|
||
--tag "${DOCKERHUB_IMAGE}:latest" \
|
||
"${DOCKERHUB_IMAGE}:${DSH_VERSION}"
|
||
|
||
- name: Verify published registry manifests
|
||
env:
|
||
DSH_VERSION: ${{ steps.signed-target.outputs.version }}
|
||
ENVIRONMENT_VERSION: ${{ steps.signed-target.outputs.environment_version }}
|
||
run: |
|
||
docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${DSH_VERSION}"
|
||
docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${DSH_VERSION}-devtools"
|
||
docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:latest"
|
||
docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:latest-devtools"
|
||
docker buildx imagetools inspect "${GHCR_IMAGE}:${ENVIRONMENT_VERSION}"
|
||
docker buildx imagetools inspect "${GHCR_IMAGE}:dsh-${DSH_VERSION}"
|
||
|
||
outputs:
|
||
dsh_version: ${{ steps.signed-target.outputs.version }}
|
||
environment_version: ${{ steps.signed-target.outputs.environment_version }}
|
||
sequence: ${{ steps.signed-target.outputs.sequence }}
|
||
|
||
notify-result:
|
||
name: Notify container image publication result
|
||
needs: build
|
||
if: >-
|
||
always() &&
|
||
(needs.build.result == 'success' || needs.build.result == 'failure')
|
||
uses: yjrszcq/github-workflows/.github/workflows/gotify-notify.yml@c4c7fe9e17854cd4962913ac2792513eab0be988
|
||
with:
|
||
title: >-
|
||
${{
|
||
needs.build.result == 'success' &&
|
||
format('DSH {0} 镜像已发布', needs.build.outputs.dsh_version) ||
|
||
'DSH 镜像发布失败'
|
||
}}
|
||
message: >-
|
||
${{
|
||
needs.build.result == 'success' &&
|
||
format('DSH {0} 与 Environment {1} 的标准镜像已同步到 Docker Hub 和 GHCR,Devtools 镜像已发布到 Docker Hub;签名目标序列为 {2}。', needs.build.outputs.dsh_version, needs.build.outputs.environment_version, needs.build.outputs.sequence) ||
|
||
format('Docker Hub 或 GHCR 镜像的构建、测试、推送或验证失败。请查看 GitHub Actions:{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id)
|
||
}}
|
||
priority: ${{ needs.build.result == 'failure' && 8 || 7 }}
|
||
secrets:
|
||
gotify_url: ${{ secrets.GOTIFY_URL }}
|
||
gotify_token: ${{ secrets.GOTIFY_TOKEN }}
|