dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x
This commit is contained in:
commit
e6dcef9bf5
302 files changed
+75178
No files matched your search
+331
@@ -0,0 +1,331 @@
|
||||
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 }}
|
||||
@@ -0,0 +1,133 @@
|
||||
name: DSH Candidate Validation
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
description: Candidate commit to validate
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Branch, tag, or commit to validate (defaults to the selected workflow ref)
|
||||
required: false
|
||||
type: string
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths:
|
||||
- release/supported-target.json
|
||||
- container/**
|
||||
- Dockerfile
|
||||
- docker-compose.yaml
|
||||
- .github/workflows/dsh-candidate-validation.yml
|
||||
|
||||
concurrency:
|
||||
group: dsh-candidate-${{ inputs.ref || github.event.pull_request.head.sha || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Build candidate with current Environment
|
||||
if: >-
|
||||
github.event_name != 'pull_request_target' ||
|
||||
github.event.pull_request.head.ref != 'automation/dsh-upstream-candidate'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout candidate
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: |
|
||||
container/control-plane/services/management/package-lock.json
|
||||
container/environment/resources/plugins/platform-management/package/package-lock.json
|
||||
|
||||
- name: Install Management runtime dependencies
|
||||
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: Resolve and verify candidate
|
||||
id: candidate
|
||||
run: |
|
||||
result="$(node container/platform/tools/supported-target.mjs validate \
|
||||
release/supported-target.json container/environment/definition.json)"
|
||||
version="$(jq -er '.target.latestSupportedDsh' <<<"$result")"
|
||||
environment="$(jq -er '.target.environment' <<<"$result")"
|
||||
registry_version="$(npm view "@deepseek-ai/dsh@${version}" version --json | jq -er 'if type == "string" then . else error("expected one version") end')"
|
||||
test "$registry_version" = "$version"
|
||||
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
|
||||
printf 'environment=%s\n' "$environment" >> "$GITHUB_OUTPUT"
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
printf '### Candidate Runtime\n\n'
|
||||
printf -- '- DSH: `%s`\n' "$version"
|
||||
printf -- '- Environment: `%s`\n' "$environment"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Verify upstream npm Artifact integrity
|
||||
env:
|
||||
DSH_VERSION: ${{ steps.candidate.outputs.version }}
|
||||
run: |
|
||||
mkdir "$RUNNER_TEMP/dsh-package"
|
||||
expected="$(npm view "@deepseek-ai/dsh@${DSH_VERSION}" dist.integrity --json | jq -er 'if type == "string" and startswith("sha512-") then . else error("missing SHA-512 integrity") end')"
|
||||
npm pack "@deepseek-ai/dsh@${DSH_VERSION}" --pack-destination "$RUNNER_TEMP/dsh-package" >/dev/null
|
||||
tarball="$(find "$RUNNER_TEMP/dsh-package" -maxdepth 1 -type f -name '*.tgz' -print -quit)"
|
||||
test -n "$tarball"
|
||||
actual="sha512-$(openssl dgst -sha512 -binary "$tarball" | base64 -w 0)"
|
||||
test "$actual" = "$expected"
|
||||
|
||||
- name: Run static compatibility checks
|
||||
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: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build candidate smoke image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
load: true
|
||||
platforms: linux/amd64
|
||||
provenance: false
|
||||
build-args: |
|
||||
DSH_VERSION=${{ steps.candidate.outputs.version }}
|
||||
tags: dsh-candidate:${{ github.run_id }}
|
||||
|
||||
- name: Run candidate container smoke checks
|
||||
run: container/test/container-smoke.sh "dsh-candidate:${{ github.run_id }}"
|
||||
|
||||
- name: Build candidate devtools image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
load: true
|
||||
platforms: linux/amd64
|
||||
provenance: false
|
||||
build-args: |
|
||||
DSH_VERSION=${{ steps.candidate.outputs.version }}
|
||||
INSTALL_DEVTOOLS=true
|
||||
tags: dsh-candidate-devtools:${{ github.run_id }}
|
||||
|
||||
- name: Run candidate devtools smoke checks
|
||||
run: container/test/devtools-smoke.sh "dsh-candidate-devtools:${{ github.run_id }}"
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
name: DSH Upstream Update
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 4 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: dsh-upstream-update
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
BASE_BRANCH: dev
|
||||
CANDIDATE_BRANCH: automation/dsh-upstream-candidate
|
||||
DSH_PACKAGE: "@deepseek-ai/dsh"
|
||||
SUPPORTED_TARGET: release/supported-target.json
|
||||
ENVIRONMENT_DEFINITION: container/environment/definition.json
|
||||
|
||||
jobs:
|
||||
discover:
|
||||
name: Discover and propose upstream DSH
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
outputs:
|
||||
changed: ${{ steps.candidate.outputs.changed }}
|
||||
current: ${{ steps.candidate.outputs.current }}
|
||||
upstream: ${{ steps.candidate.outputs.upstream }}
|
||||
environment: ${{ steps.candidate.outputs.environment }}
|
||||
pull_request_url: ${{ steps.propose.outputs.pull_request_url }}
|
||||
pull_request_number: ${{ steps.propose.outputs.pull_request_number }}
|
||||
head_sha: ${{ steps.propose.outputs.head_sha }}
|
||||
|
||||
steps:
|
||||
- name: Checkout protected branch
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.BASE_BRANCH }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Resolve latest official DSH
|
||||
id: upstream
|
||||
run: |
|
||||
npm view "${DSH_PACKAGE}@latest" version --json > "$RUNNER_TEMP/dsh-version.json"
|
||||
version="$(jq -er 'if type == "string" then . else error("expected one version") end' "$RUNNER_TEMP/dsh-version.json")"
|
||||
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Advance candidate Supported Target
|
||||
id: candidate
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
UPSTREAM_VERSION: ${{ steps.upstream.outputs.version }}
|
||||
run: |
|
||||
base_sha="$(git rev-parse HEAD)"
|
||||
current="$(jq -er '.latestSupportedDsh' "$SUPPORTED_TARGET")"
|
||||
trusted_platform="$RUNNER_TEMP/trusted-platform"
|
||||
mkdir -p "$trusted_platform/tools"
|
||||
cp -R container/platform/lib "$trusted_platform/lib"
|
||||
cp container/platform/tools/supported-target.mjs "$trusted_platform/tools/supported-target.mjs"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
candidate_remote_sha="$(git ls-remote --heads origin "$CANDIDATE_BRANCH" | cut -f 1)"
|
||||
git switch --create "$CANDIDATE_BRANCH" "$base_sha"
|
||||
result="$(node "$trusted_platform/tools/supported-target.mjs" advance \
|
||||
"$SUPPORTED_TARGET" "$ENVIRONMENT_DEFINITION" "$UPSTREAM_VERSION")"
|
||||
changed="$(jq -r 'if (.changed | type) == "boolean" then .changed else error("expected boolean changed") end' <<<"$result")"
|
||||
environment="$(jq -er '.target.environment' <<<"$result")"
|
||||
{
|
||||
printf 'changed=%s\n' "$changed"
|
||||
printf 'current=%s\n' "$current"
|
||||
printf 'upstream=%s\n' "$UPSTREAM_VERSION"
|
||||
printf 'environment=%s\n' "$environment"
|
||||
printf 'candidate_remote_sha=%s\n' "$candidate_remote_sha"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
printf '### DSH upstream discovery\n\n'
|
||||
printf -- '- Latest Supported: `%s`\n' "$current"
|
||||
printf -- '- Latest Official: `%s`\n' "$UPSTREAM_VERSION"
|
||||
printf -- '- Environment: `%s`\n' "$environment"
|
||||
printf -- '- Candidate required: `%s`\n' "$changed"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Create or update candidate pull request
|
||||
id: propose
|
||||
if: steps.candidate.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CURRENT_VERSION: ${{ steps.candidate.outputs.current }}
|
||||
UPSTREAM_VERSION: ${{ steps.candidate.outputs.upstream }}
|
||||
ENVIRONMENT_VERSION: ${{ steps.candidate.outputs.environment }}
|
||||
run: |
|
||||
git add -- "$SUPPORTED_TARGET"
|
||||
git commit -m "chore(dsh): validate ${UPSTREAM_VERSION}"
|
||||
|
||||
candidate_remote_sha="${{ steps.candidate.outputs.candidate_remote_sha }}"
|
||||
if [ -n "$candidate_remote_sha" ]; then
|
||||
git push --force-with-lease="refs/heads/$CANDIDATE_BRANCH:$candidate_remote_sha" \
|
||||
origin "HEAD:refs/heads/$CANDIDATE_BRANCH"
|
||||
else
|
||||
git push origin "HEAD:refs/heads/$CANDIDATE_BRANCH"
|
||||
fi
|
||||
|
||||
body="$RUNNER_TEMP/dsh-candidate-pr.md"
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
printf '## DSH candidate validation\n\n'
|
||||
printf '| State | Version |\n|---|---|\n'
|
||||
printf '| Latest Supported | `%s` |\n' "$CURRENT_VERSION"
|
||||
printf '| Latest Official candidate | `%s` |\n' "$UPSTREAM_VERSION"
|
||||
printf '| Current Environment | `%s` |\n\n' "$ENVIRONMENT_VERSION"
|
||||
printf 'This PR changes only the candidate Supported Target. Compatibility CI validates the new DSH with the current Environment. Merge into dev records the validated candidate; promotion from dev to main remains the production publication gate.\n'
|
||||
} > "$body"
|
||||
|
||||
pr_number="$(gh pr list --state open --head "$CANDIDATE_BRANCH" --json number --jq '.[0].number // empty')"
|
||||
if [ -z "$pr_number" ]; then
|
||||
pr_url="$(gh pr create \
|
||||
--base "$BASE_BRANCH" \
|
||||
--head "$CANDIDATE_BRANCH" \
|
||||
--title "chore(dsh): validate ${UPSTREAM_VERSION}" \
|
||||
--body-file "$body")"
|
||||
pr_number="$(gh pr view "$pr_url" --json number --jq .number)"
|
||||
else
|
||||
gh pr edit "$pr_number" \
|
||||
--base "$BASE_BRANCH" \
|
||||
--title "chore(dsh): validate ${UPSTREAM_VERSION}" \
|
||||
--body-file "$body"
|
||||
pr_url="$(gh pr view "$pr_number" --json url --jq .url)"
|
||||
fi
|
||||
{
|
||||
printf 'pull_request_url=%s\n' "$pr_url"
|
||||
printf 'pull_request_number=%s\n' "$pr_number"
|
||||
printf 'head_sha=%s\n' "$(git rev-parse HEAD)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
printf 'Candidate PR: %s\n' "$pr_url" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
validate-candidate:
|
||||
name: Validate candidate Runtime
|
||||
needs: discover
|
||||
if: needs.discover.outputs.changed == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/dsh-candidate-validation.yml
|
||||
with:
|
||||
ref: ${{ needs.discover.outputs.head_sha }}
|
||||
|
||||
report-validation:
|
||||
name: Report candidate validation
|
||||
needs: [discover, validate-candidate]
|
||||
if: always() && needs.discover.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
steps:
|
||||
- name: Publish commit status and PR report
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
HEAD_SHA: ${{ needs.discover.outputs.head_sha }}
|
||||
PR_NUMBER: ${{ needs.discover.outputs.pull_request_number }}
|
||||
VALIDATION_RESULT: ${{ needs.validate-candidate.result }}
|
||||
UPSTREAM_VERSION: ${{ needs.discover.outputs.upstream }}
|
||||
ENVIRONMENT_VERSION: ${{ needs.discover.outputs.environment }}
|
||||
run: |
|
||||
if [ "$VALIDATION_RESULT" = success ]; then
|
||||
state=success
|
||||
label=passed
|
||||
description="Candidate Runtime validation passed"
|
||||
else
|
||||
state=failure
|
||||
label=failed
|
||||
description="Candidate Runtime validation failed"
|
||||
fi
|
||||
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
gh api --method POST "/repos/${GITHUB_REPOSITORY}/statuses/${HEAD_SHA}" \
|
||||
-f state="$state" \
|
||||
-f context='DSH candidate Runtime' \
|
||||
-f description="$description" \
|
||||
-f target_url="$run_url" >/dev/null
|
||||
gh pr comment "$PR_NUMBER" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--body "DSH \`${UPSTREAM_VERSION}\` with Environment \`${ENVIRONMENT_VERSION}\` **${label}** compatibility validation. [Open workflow run](${run_url})."
|
||||
printf 'Candidate validation %s: %s\n' "$label" "$run_url" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
notify-discovered:
|
||||
name: Notify upstream discovery
|
||||
needs: discover
|
||||
if: needs.discover.outputs.changed == 'true'
|
||||
uses: yjrszcq/github-workflows/.github/workflows/gotify-notify.yml@c4c7fe9e17854cd4962913ac2792513eab0be988
|
||||
with:
|
||||
title: 发现 DSH 上游新版本 ${{ needs.discover.outputs.upstream }}
|
||||
message: >-
|
||||
当前支持版本:${{ needs.discover.outputs.current }};上游候选版本:${{ needs.discover.outputs.upstream }}。
|
||||
候选 Environment:${{ needs.discover.outputs.environment }}。
|
||||
候选 PR:${{ needs.discover.outputs.pull_request_url }}
|
||||
priority: 6
|
||||
secrets:
|
||||
gotify_url: ${{ secrets.GOTIFY_URL }}
|
||||
gotify_token: ${{ secrets.GOTIFY_TOKEN }}
|
||||
|
||||
notify-result:
|
||||
name: Notify upstream check result
|
||||
needs: [discover, validate-candidate]
|
||||
if: >-
|
||||
always() &&
|
||||
(needs.discover.result == 'failure' || needs.discover.outputs.changed == 'true')
|
||||
uses: yjrszcq/github-workflows/.github/workflows/gotify-notify.yml@c4c7fe9e17854cd4962913ac2792513eab0be988
|
||||
with:
|
||||
title: >-
|
||||
${{
|
||||
needs.discover.result == 'failure' && 'DSH 上游检查失败' ||
|
||||
(needs.validate-candidate.result == 'success' &&
|
||||
format('DSH {0} 候选版本验证通过', needs.discover.outputs.upstream) ||
|
||||
format('DSH {0} 候选版本验证失败', needs.discover.outputs.upstream))
|
||||
}}
|
||||
message: >-
|
||||
${{
|
||||
needs.discover.result == 'failure' &&
|
||||
format('获取或生成 DSH 上游候选版本失败。工作流:{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) ||
|
||||
(needs.validate-candidate.result == 'success' &&
|
||||
format('DSH {0} 已通过 Environment {1} 兼容性验证。请审核并合并:{2}', needs.discover.outputs.upstream, needs.discover.outputs.environment, needs.discover.outputs.pull_request_url) ||
|
||||
format('DSH {0} 未通过 Environment {1} 兼容性验证,需要适配 Environment。工作流:{2}/{3}/actions/runs/{4}', needs.discover.outputs.upstream, needs.discover.outputs.environment, github.server_url, github.repository, github.run_id))
|
||||
}}
|
||||
priority: ${{ (needs.discover.result == 'failure' || needs.validate-candidate.result != 'success') && 8 || 7 }}
|
||||
secrets:
|
||||
gotify_url: ${{ secrets.GOTIFY_URL }}
|
||||
gotify_token: ${{ secrets.GOTIFY_TOKEN }}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
name: Publish Supported Platform Target
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- container/environment/definition.json
|
||||
- release/supported-target.json
|
||||
- release/official-dsh-policy.json
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: production-supported-target
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Sign and publish Supported Target
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
environment: production-release
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout protected branch
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: |
|
||||
container/control-plane/services/management/package-lock.json
|
||||
container/environment/resources/plugins/platform-management/package/package-lock.json
|
||||
|
||||
- name: Install Management runtime dependencies
|
||||
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 merged Supported Target
|
||||
id: target
|
||||
run: |
|
||||
result="$(node container/platform/tools/supported-target.mjs validate \
|
||||
release/supported-target.json container/environment/definition.json)"
|
||||
dsh_version="$(jq -er '.target.latestSupportedDsh' <<<"$result")"
|
||||
environment_version="$(jq -er '.target.environment' <<<"$result")"
|
||||
[[ "$environment_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
||||
printf 'dsh_version=%s\n' "$dsh_version" >> "$GITHUB_OUTPUT"
|
||||
printf 'environment_version=%s\n' "$environment_version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run release contract tests
|
||||
run: |
|
||||
npm test --prefix container/platform
|
||||
npm test --prefix container/control-plane/services/gateway
|
||||
node container/test/compose-config.mjs
|
||||
|
||||
- name: Install protected public trust bundle
|
||||
env:
|
||||
RECOVERY_ROOT_PUBLIC_KEY: ${{ secrets.DSH_RECOVERY_ROOT_PUBLIC_KEY }}
|
||||
KEYRING_JSON_BASE64: ${{ secrets.DSH_KEYRING_JSON_BASE64 }}
|
||||
KEYRING_SIGNATURE_BASE64: ${{ secrets.DSH_KEYRING_SIGNATURE_BASE64 }}
|
||||
run: |
|
||||
test -n "$RECOVERY_ROOT_PUBLIC_KEY"
|
||||
test -n "$KEYRING_JSON_BASE64"
|
||||
test -n "$KEYRING_SIGNATURE_BASE64"
|
||||
mkdir -p "$RUNNER_TEMP/production-trust"
|
||||
printf '%s\n' "$RECOVERY_ROOT_PUBLIC_KEY" > "$RUNNER_TEMP/production-trust/recovery-root.spki.base64"
|
||||
printf '%s' "$KEYRING_JSON_BASE64" | base64 --decode > "$RUNNER_TEMP/production-trust/keyring.json"
|
||||
printf '%s' "$KEYRING_SIGNATURE_BASE64" | base64 --decode > "$RUNNER_TEMP/production-trust/keyring.sig.json"
|
||||
|
||||
- name: Resolve signed channel and target sequence
|
||||
id: sequence
|
||||
run: |
|
||||
channel="$RUNNER_TEMP/release-channel"
|
||||
keyring_sha256="$(sha256sum "$RUNNER_TEMP/production-trust/keyring.json" | cut -d ' ' -f 1)"
|
||||
previous=-
|
||||
reuse=false
|
||||
if git ls-remote --exit-code origin refs/heads/release-channel >/dev/null 2>&1; then
|
||||
git fetch origin refs/heads/release-channel
|
||||
channel_commit="$(git rev-parse FETCH_HEAD)"
|
||||
git worktree add --detach "$channel" "$channel_commit"
|
||||
current_sequence="$(jq -er .targetSequence "$channel/stable.json")"
|
||||
previous="$channel/targets/$current_sequence"
|
||||
test -d "$previous"
|
||||
for name in \
|
||||
stable.json \
|
||||
stable.sig.json \
|
||||
keyring.json \
|
||||
keyring.sig.json \
|
||||
bootstrap.manifest.json \
|
||||
bootstrap.manifest.sig.json; do
|
||||
test -f "$previous/$name"
|
||||
done
|
||||
for name in stable.json stable.sig.json keyring.json keyring.sig.json; do
|
||||
cmp "$channel/$name" "$previous/$name"
|
||||
done
|
||||
if test -f "$channel/channel.json" \
|
||||
&& test "$(jq -er .sourceCommit "$channel/channel.json")" = "$GITHUB_SHA" \
|
||||
&& test "$(jq -er .keyringSha256 "$channel/channel.json")" = "$keyring_sha256"; then
|
||||
sequence="$current_sequence"
|
||||
reuse=true
|
||||
else
|
||||
test "$current_sequence" -lt 9007199254740991
|
||||
sequence="$((current_sequence + 1))"
|
||||
fi
|
||||
else
|
||||
git worktree add --detach "$channel" "$GITHUB_SHA"
|
||||
git -C "$channel" checkout --orphan release-channel
|
||||
git -C "$channel" rm -rf .
|
||||
sequence=1
|
||||
channel_commit=
|
||||
fi
|
||||
{
|
||||
printf 'channel=%s\n' "$channel"
|
||||
printf 'channel_commit=%s\n' "$channel_commit"
|
||||
printf 'keyring_sha256=%s\n' "$keyring_sha256"
|
||||
printf 'previous=%s\n' "$previous"
|
||||
printf 'reuse=%s\n' "$reuse"
|
||||
printf 'sequence=%s\n' "$sequence"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
printf 'Target sequence: %s (reuse: %s)\n' "$sequence" "$reuse" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Download reviewed DSH npm Artifact
|
||||
if: steps.sequence.outputs.reuse != 'true'
|
||||
env:
|
||||
DSH_VERSION: ${{ steps.target.outputs.dsh_version }}
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/platform-release-input"
|
||||
npm pack "@deepseek-ai/dsh@${DSH_VERSION}" --pack-destination "$RUNNER_TEMP/platform-release-input" >/dev/null
|
||||
tarball="$(find "$RUNNER_TEMP/platform-release-input" -maxdepth 1 -type f -name '*.tgz' -print -quit)"
|
||||
test -n "$tarball"
|
||||
printf 'DSH_TARBALL=%s\n' "$tarball" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Prepare signed immutable target
|
||||
if: steps.sequence.outputs.reuse != 'true'
|
||||
env:
|
||||
RELEASE_PRIVATE_KEY: ${{ secrets.DSH_RELEASE_PRIVATE_KEY }}
|
||||
TARGET_SEQUENCE: ${{ steps.sequence.outputs.sequence }}
|
||||
PREVIOUS_STABLE: ${{ steps.sequence.outputs.previous }}
|
||||
run: |
|
||||
test -n "$RELEASE_PRIVATE_KEY"
|
||||
umask 077
|
||||
private_key="$RUNNER_TEMP/current-release-private.pem"
|
||||
trap 'rm -f "$private_key"' EXIT
|
||||
printf '%s\n' "$RELEASE_PRIVATE_KEY" > "$private_key"
|
||||
artifact_base_url="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/release-channel/targets/${TARGET_SEQUENCE}/"
|
||||
SOURCE_DATE_EPOCH="$(git show -s --format=%ct "$GITHUB_SHA")"
|
||||
export SOURCE_DATE_EPOCH
|
||||
node container/platform/tools/prepare-release.mjs \
|
||||
release/supported-target.json container/environment/definition.json release/official-dsh-policy.json \
|
||||
"$RUNNER_TEMP/production-trust" "$private_key" "$DSH_TARBALL" "$PREVIOUS_STABLE" \
|
||||
"$TARGET_SEQUENCE" "$artifact_base_url" "$RUNNER_TEMP/platform-release"
|
||||
rm -f "$private_key"
|
||||
trap - EXIT
|
||||
|
||||
- name: Reuse an already published target after retry
|
||||
if: steps.sequence.outputs.reuse == 'true'
|
||||
env:
|
||||
TARGET_SEQUENCE: ${{ steps.sequence.outputs.sequence }}
|
||||
CHANNEL: ${{ steps.sequence.outputs.channel }}
|
||||
DSH_VERSION: ${{ steps.target.outputs.dsh_version }}
|
||||
ENVIRONMENT_VERSION: ${{ steps.target.outputs.environment_version }}
|
||||
run: |
|
||||
cp -a "$CHANNEL/targets/$TARGET_SEQUENCE" "$RUNNER_TEMP/platform-release"
|
||||
test "$(jq -er .desired.dsh.version "$RUNNER_TEMP/platform-release/stable.json")" = "$DSH_VERSION"
|
||||
test "$(jq -er .desired.environment.version "$RUNNER_TEMP/platform-release/stable.json")" = "$ENVIRONMENT_VERSION"
|
||||
|
||||
- name: Derive Environment release identity
|
||||
run: |
|
||||
node container/platform/tools/environment-release.mjs \
|
||||
"$RUNNER_TEMP/platform-release/bootstrap.manifest.json" \
|
||||
"$RUNNER_TEMP/platform-release/environment.manifest.json" \
|
||||
release/official-dsh-policy.json \
|
||||
"$RUNNER_TEMP/environment-release.json"
|
||||
|
||||
- name: Validate Environment version ownership
|
||||
id: environment-check
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ENVIRONMENT_VERSION: ${{ steps.target.outputs.environment_version }}
|
||||
run: |
|
||||
tag="v${ENVIRONMENT_VERSION}"
|
||||
publish=true
|
||||
if gh release view "$tag" --json isDraft > "$RUNNER_TEMP/environment-release-state.json" 2>/dev/null; then
|
||||
if test "$(jq -er .isDraft "$RUNNER_TEMP/environment-release-state.json")" = false; then
|
||||
mkdir -p "$RUNNER_TEMP/published-environment"
|
||||
gh release download "$tag" --pattern environment-release.json --dir "$RUNNER_TEMP/published-environment"
|
||||
cmp "$RUNNER_TEMP/environment-release.json" "$RUNNER_TEMP/published-environment/environment-release.json"
|
||||
publish=false
|
||||
else
|
||||
mkdir -p "$RUNNER_TEMP/draft-environment"
|
||||
if gh release download "$tag" --pattern environment-release.json --dir "$RUNNER_TEMP/draft-environment" 2>/dev/null; then
|
||||
cmp "$RUNNER_TEMP/environment-release.json" "$RUNNER_TEMP/draft-environment/environment-release.json"
|
||||
fi
|
||||
fi
|
||||
elif git ls-remote --exit-code origin "refs/tags/$tag" >/dev/null 2>&1; then
|
||||
echo "Git tag $tag exists without a matching Environment Release" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'publish=%s\n' "$publish" >> "$GITHUB_OUTPUT"
|
||||
printf 'tag=%s\n' "$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Publish signed channel target
|
||||
env:
|
||||
CHANNEL: ${{ steps.sequence.outputs.channel }}
|
||||
EXISTING_CHANNEL_COMMIT: ${{ steps.sequence.outputs.channel_commit }}
|
||||
KEYRING_SHA256: ${{ steps.sequence.outputs.keyring_sha256 }}
|
||||
REUSE: ${{ steps.sequence.outputs.reuse }}
|
||||
TARGET_SEQUENCE: ${{ steps.sequence.outputs.sequence }}
|
||||
DSH_VERSION: ${{ steps.target.outputs.dsh_version }}
|
||||
ENVIRONMENT_VERSION: ${{ steps.target.outputs.environment_version }}
|
||||
run: |
|
||||
if test "$REUSE" = true; then
|
||||
commit="$EXISTING_CHANNEL_COMMIT"
|
||||
else
|
||||
target="$CHANNEL/targets/$TARGET_SEQUENCE"
|
||||
test ! -e "$target"
|
||||
mkdir -p "$target"
|
||||
cp -a "$RUNNER_TEMP/platform-release/." "$target/"
|
||||
for name in stable.json stable.sig.json keyring.json keyring.sig.json; do cp "$target/$name" "$CHANNEL/$name"; done
|
||||
# shellcheck disable=SC2016
|
||||
jq -n --arg sourceCommit "$GITHUB_SHA" --arg keyringSha256 "$KEYRING_SHA256" \
|
||||
--arg dshVersion "$DSH_VERSION" --arg environmentVersion "$ENVIRONMENT_VERSION" \
|
||||
--argjson targetSequence "$TARGET_SEQUENCE" \
|
||||
'{schema:1,sourceCommit:$sourceCommit,keyringSha256:$keyringSha256,targetSequence:$targetSequence,dshVersion:$dshVersion,environmentVersion:$environmentVersion}' \
|
||||
> "$CHANNEL/channel.json"
|
||||
git -C "$CHANNEL" config user.name github-actions
|
||||
git -C "$CHANNEL" config user.email github-actions@github.com
|
||||
git -C "$CHANNEL" add channel.json keyring.json keyring.sig.json stable.json stable.sig.json "targets/$TARGET_SEQUENCE"
|
||||
git -C "$CHANNEL" commit -m "release: publish target sequence $TARGET_SEQUENCE"
|
||||
commit="$(git -C "$CHANNEL" rev-parse HEAD)"
|
||||
git -C "$CHANNEL" push origin HEAD:refs/heads/release-channel
|
||||
fi
|
||||
# shellcheck disable=SC2016
|
||||
printf 'Published signed channel sequence `%s` at `%s`.\n' "$TARGET_SEQUENCE" "$commit" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Publish Environment Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PUBLISH_ENVIRONMENT: ${{ steps.environment-check.outputs.publish }}
|
||||
ENVIRONMENT_TAG: ${{ steps.environment-check.outputs.tag }}
|
||||
ENVIRONMENT_VERSION: ${{ steps.target.outputs.environment_version }}
|
||||
DSH_VERSION: ${{ steps.target.outputs.dsh_version }}
|
||||
TARGET_SEQUENCE: ${{ steps.sequence.outputs.sequence }}
|
||||
run: |
|
||||
prune_environment_assets() {
|
||||
gh release view "$ENVIRONMENT_TAG" --json assets --jq '.assets[].name' | while IFS= read -r name; do
|
||||
if test "$name" != environment-release.json; then
|
||||
gh release delete-asset "$ENVIRONMENT_TAG" "$name" --yes
|
||||
fi
|
||||
done
|
||||
}
|
||||
if test "$PUBLISH_ENVIRONMENT" != true; then
|
||||
prune_environment_assets
|
||||
test "$(gh release view "$ENVIRONMENT_TAG" --json assets --jq '.assets[].name')" = environment-release.json
|
||||
exit 0
|
||||
fi
|
||||
assets="$RUNNER_TEMP/environment-release-assets"
|
||||
mkdir -p "$assets"
|
||||
cp "$RUNNER_TEMP/environment-release.json" "$assets/"
|
||||
notes="$RUNNER_TEMP/environment-release-notes.md"
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
printf 'Container Environment: `%s`\n\n' "$ENVIRONMENT_VERSION"
|
||||
printf 'Supported DSH at publication: `%s`\n\n' "$DSH_VERSION"
|
||||
printf 'Initial signed target sequence: `%s`\n' "$TARGET_SEQUENCE"
|
||||
} > "$notes"
|
||||
if ! gh release view "$ENVIRONMENT_TAG" >/dev/null 2>&1; then
|
||||
gh release create "$ENVIRONMENT_TAG" --draft --target "$GITHUB_SHA" \
|
||||
--title "$ENVIRONMENT_TAG" --notes-file "$notes"
|
||||
fi
|
||||
gh release edit "$ENVIRONMENT_TAG" --draft --target "$GITHUB_SHA" \
|
||||
--title "$ENVIRONMENT_TAG" --notes-file "$notes"
|
||||
gh release upload "$ENVIRONMENT_TAG" "$assets"/* --clobber
|
||||
prune_environment_assets
|
||||
find "$assets" -maxdepth 1 -type f -printf '%f\n' | sort > "$RUNNER_TEMP/local-environment-assets"
|
||||
gh release view "$ENVIRONMENT_TAG" --json assets --jq '.assets[].name' | sort > "$RUNNER_TEMP/remote-environment-assets"
|
||||
diff -u "$RUNNER_TEMP/local-environment-assets" "$RUNNER_TEMP/remote-environment-assets"
|
||||
gh release edit "$ENVIRONMENT_TAG" --draft=false --latest \
|
||||
--title "$ENVIRONMENT_TAG" --notes-file "$notes"
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Notify Workflow Results
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Publish Supported Platform Target
|
||||
- DSH Candidate Validation
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
notify-result:
|
||||
name: Notify completed workflow result
|
||||
if: >-
|
||||
(github.event.workflow_run.name == 'Publish Supported Platform Target' &&
|
||||
(github.event.workflow_run.conclusion == 'success' ||
|
||||
github.event.workflow_run.conclusion == 'failure')) ||
|
||||
(github.event.workflow_run.name == 'DSH Candidate Validation' &&
|
||||
(github.event.workflow_run.event == 'pull_request_target' ||
|
||||
github.event.workflow_run.event == 'workflow_dispatch') &&
|
||||
github.event.workflow_run.head_branch != 'automation/dsh-upstream-candidate' &&
|
||||
(github.event.workflow_run.conclusion == 'success' ||
|
||||
github.event.workflow_run.conclusion == 'failure'))
|
||||
uses: yjrszcq/github-workflows/.github/workflows/gotify-notify.yml@c4c7fe9e17854cd4962913ac2792513eab0be988
|
||||
with:
|
||||
title: >-
|
||||
${{
|
||||
github.event.workflow_run.name == 'Publish Supported Platform Target' &&
|
||||
(github.event.workflow_run.conclusion == 'success' && 'DSH 正式目标已发布' || 'DSH 正式发布失败') ||
|
||||
(github.event.workflow_run.conclusion == 'success' && 'DSH 候选版本验证通过' || 'DSH 候选版本验证失败')
|
||||
}}
|
||||
message: >-
|
||||
${{
|
||||
github.event.workflow_run.name == 'Publish Supported Platform Target' &&
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
format('签名目标和必要的 Environment Release 已发布。独立镜像工作流已经启动;如果 production-image 启用了审批,镜像发布会等待批准。镜像工作流:{0}/{1}/actions/workflows/docker.yaml', github.server_url, github.repository) ||
|
||||
format('签名目标或 Environment Release 发布失败。工作流:{0}', github.event.workflow_run.html_url)) ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
(github.event.workflow_run.event == 'workflow_dispatch' &&
|
||||
format('手动发起的 DSH 候选版本兼容性验证通过。候选提交:{0}。工作流:{1}', github.event.workflow_run.head_sha, github.event.workflow_run.html_url) ||
|
||||
format('Pull Request 的 DSH 候选版本兼容性验证通过。候选提交:{0}。工作流:{1}', github.event.workflow_run.head_sha, github.event.workflow_run.html_url)) ||
|
||||
(github.event.workflow_run.event == 'workflow_dispatch' &&
|
||||
format('手动发起的 DSH 候选版本兼容性验证失败。候选提交:{0}。工作流:{1}', github.event.workflow_run.head_sha, github.event.workflow_run.html_url) ||
|
||||
format('Pull Request 的 DSH 候选版本兼容性验证失败。候选提交:{0}。工作流:{1}', github.event.workflow_run.head_sha, github.event.workflow_run.html_url)))
|
||||
}}
|
||||
priority: ${{ github.event.workflow_run.conclusion == 'failure' && 8 || 7 }}
|
||||
secrets:
|
||||
gotify_url: ${{ secrets.GOTIFY_URL }}
|
||||
gotify_token: ${{ secrets.GOTIFY_TOKEN }}
|
||||
Reference in new issue
Block a user