ci: action matrix release (#3761)

* ci: serialize release image workflows

* feat(build): add single-service Docker build script

* chore(ci): remove draft release publishing step

# Conflicts:
#	.github/workflows/update-version-file-on-release.yml
main
dsx137 14 hours ago committed by GitHub
parent 10a577c310
commit de0f1eec8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4,18 +4,52 @@ on:
push:
branches:
- release-*
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
default: "v3.8.3"
workflow_call:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
type: string
checkout_sha:
description: "Commit SHA to checkout"
required: true
type: string
checkout_short_sha:
description: "Short commit SHA for image tags"
required: true
type: string
jobs:
services:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.services.outputs.services }}
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.0
with:
ref: ${{ inputs.checkout_sha || inputs.tag || github.event.inputs.tag }}
- name: Resolve service matrix
id: services
run: |
set -euo pipefail
services=$(docker compose -f build/images/openim-server/docker-compose.build.yml config --services | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "services=$services" >> "$GITHUB_OUTPUT"
build-and-push:
needs: services
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service: ${{ fromJson(needs.services.outputs.services) }}
permissions:
contents: read
packages: write
@ -23,6 +57,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.0
with:
ref: ${{ inputs.checkout_sha || inputs.tag || github.event.inputs.tag }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4.1.0
@ -53,8 +89,9 @@ jobs:
- name: Resolve Docker image tags
id: tags
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
INPUT_TAG: ${{ inputs.tag || github.event.inputs.tag }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
CHECKOUT_SHORT_SHA: ${{ inputs.checkout_short_sha }}
run: |
set -euo pipefail
@ -84,13 +121,17 @@ jobs:
add_tag_with_semver_alias "${INPUT_TAG:-}"
add_tag_with_semver_alias "${RELEASE_TAG:-}"
if [[ "${GITHUB_EVENT_NAME:-}" == "release" && "${{ github.event.release.prerelease }}" != "true" ]]; then
add_tag "latest"
fi
if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
add_tag_with_semver_alias "${GITHUB_REF_NAME}"
elif [[ "${GITHUB_REF_TYPE:-}" == "branch" ]]; then
add_tag "${GITHUB_REF_NAME}"
fi
add_tag "sha-${GITHUB_SHA::7}"
add_tag "sha-${CHECKOUT_SHORT_SHA:-${GITHUB_SHA::7}}"
{
echo "tags<<EOF"
@ -102,6 +143,7 @@ jobs:
env:
RELEASE: "true"
PUSH: "true"
SERVICE: ${{ matrix.service }}
PLATFORMS: linux/amd64,linux/arm64
IMAGE_TAGS: ${{ steps.tags.outputs.tags }}
IMAGE_REGISTRIES: |
@ -109,4 +151,4 @@ jobs:
ghcr.io/${{ github.repository_owner }}
registry.cn-hangzhou.aliyuncs.com/openimsdk
run: |
bash build/build.sh
bash build/build_single.sh

@ -4,14 +4,26 @@ on:
push:
branches:
- release-*
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
default: "v3.8.3"
workflow_call:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
type: string
checkout_sha:
description: "Commit SHA to checkout"
required: true
type: string
checkout_short_sha:
description: "Short commit SHA for image tags"
required: true
type: string
env:
IMAGE_NAME: "openim-server"
@ -21,11 +33,15 @@ env:
jobs:
publish-docker-images:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.merged == false) }}
steps:
- name: Checkout main repository
uses: actions/checkout@v7.0.0
with:
ref: ${{ inputs.checkout_sha || inputs.tag || github.event.inputs.tag }}
path: main-repo
- name: Set up QEMU
@ -54,7 +70,10 @@ jobs:
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
type=sha,enable=${{ inputs.checkout_short_sha == '' }}
type=raw,value=sha-${{ inputs.checkout_short_sha }},enable=${{ inputs.checkout_short_sha != '' }}
type=raw,value=${{ inputs.tag || github.event.inputs.tag }}
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}
- name: Install skopeo
run: |

@ -0,0 +1,56 @@
name: Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Existing tag to release"
required: true
target_branch:
description: "Branch that the release tag was created from"
required: true
permissions:
actions: write
contents: write
packages: write
concurrency:
group: release-${{ github.event.release.tag_name || inputs.tag }}
cancel-in-progress: false
jobs:
update-version:
permissions:
contents: write
uses: ./.github/workflows/update-version-file-on-release.yml
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
target_branch: ${{ github.event.release.target_commitish || inputs.target_branch }}
secrets: inherit
docker-image:
needs: update-version
permissions:
contents: read
packages: write
uses: ./.github/workflows/publish-docker-image.yml
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
checkout_sha: ${{ needs.update-version.outputs.updated_sha }}
checkout_short_sha: ${{ needs.update-version.outputs.updated_short_sha }}
secrets: inherit
service-images:
needs: update-version
permissions:
contents: read
packages: write
uses: ./.github/workflows/docker-build-and-release-services-images.yml
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
checkout_sha: ${{ needs.update-version.outputs.updated_sha }}
checkout_short_sha: ${{ needs.update-version.outputs.updated_short_sha }}
secrets: inherit

@ -1,22 +1,66 @@
name: Update Version File on Release
on:
release:
types: [created]
workflow_dispatch:
inputs:
tag:
description: "Tag version to update"
required: true
target_branch:
description: "Branch that the release tag was created from"
required: true
workflow_call:
inputs:
tag:
description: "Tag version to update"
required: true
type: string
target_branch:
description: "Branch that the release tag was created from"
required: true
type: string
outputs:
updated_sha:
description: "Commit SHA after updating version and moving the tag"
value: ${{ jobs.update-version.outputs.updated_sha }}
updated_short_sha:
description: "Short commit SHA after updating version and moving the tag"
value: ${{ jobs.update-version.outputs.updated_short_sha }}
permissions:
contents: write
jobs:
update-version:
runs-on: ubuntu-latest
outputs:
updated_sha: ${{ steps.resolve-tag.outputs.updated_sha }}
updated_short_sha: ${{ steps.resolve-tag.outputs.updated_short_sha }}
env:
TAG_VERSION: ${{ github.event.release.tag_name }}
TAG_VERSION: ${{ inputs.tag || github.event.inputs.tag }}
TARGET_BRANCH: ${{ inputs.target_branch || github.event.inputs.target_branch }}
steps:
# Step 1: Checkout the original repository's code
- name: Checkout code
uses: actions/checkout@v7.0.0
with:
ref: ${{ inputs.target_branch || github.event.inputs.target_branch }}
fetch-depth: 0
# submodules: "recursive"
- name: Validate target branch
run: |
set -euo pipefail
if [[ -z "${TARGET_BRANCH}" ]]; then
echo "target_branch is required"
exit 1
fi
if ! git ls-remote --exit-code --heads origin "${TARGET_BRANCH}" >/dev/null; then
echo "target_branch must be an existing branch: ${TARGET_BRANCH}"
exit 1
fi
git checkout -B "${TARGET_BRANCH}" "origin/${TARGET_BRANCH}"
- name: Safe submodule initialization
run: |
echo "Checking for submodules..."
@ -48,7 +92,6 @@ jobs:
run: |
if git rev-parse ${{ env.TAG_VERSION }} >/dev/null 2>&1; then
git tag -d ${{ env.TAG_VERSION }}
git push --delete origin ${{ env.TAG_VERSION }}
fi
# Step 4: Update version file
@ -62,58 +105,26 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
git add version/version
git commit -m "Update version to ${{ env.TAG_VERSION }}"
if git diff --cached --quiet; then
echo "version/version already matches ${TAG_VERSION}"
else
git commit -m "Update version to ${{ env.TAG_VERSION }}"
git push origin HEAD:${TARGET_BRANCH}
fi
# Step 6: Update tag
- name: Update tag
run: |
set -euo pipefail
git tag -fa ${{ env.TAG_VERSION }} -m "Update version to ${{ env.TAG_VERSION }}"
git push origin ${{ env.TAG_VERSION }} --force
git push origin refs/tags/${{ env.TAG_VERSION }}:refs/tags/${{ env.TAG_VERSION }} --force
# Step 7: Find and Publish Draft Release
- name: Find and Publish Draft Release
uses: actions/github-script@v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const tagName = process.env.TAG_VERSION;
- name: Resolve updated tag SHA
id: resolve-tag
run: |
updated_sha="$(git rev-parse "${TAG_VERSION}^{commit}")"
echo "updated_sha=$updated_sha" >> "$GITHUB_OUTPUT"
echo "updated_short_sha=${updated_sha:0:12}" >> "$GITHUB_OUTPUT"
try {
let release;
try {
const response = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: tagName
});
release = response.data;
} catch (tagError) {
core.info(`Release not found by tag, searching all releases...`);
const releases = await github.rest.repos.listReleases({
owner,
repo,
per_page: 100
});
release = releases.data.find(r => r.draft && r.tag_name === tagName);
if (!release) {
throw new Error(`No release found with tag ${tagName}`);
}
}
await github.rest.repos.updateRelease({
owner,
repo,
release_id: release.id,
draft: false,
prerelease: release.prerelease
});
const status = release.draft ? "was draft" : "was already published";
core.info(`Release ${tagName} ensured to be published (${status}).`);
} catch (error) {
core.warning(`Could not find or update release for tag ${tagName}: ${error.message}`);
}

@ -0,0 +1,105 @@
#!/usr/bin/env bash
set -euo pipefail
CYAN='\033[0;36m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NO_COLOR='\033[0m'
BASE_DIR="$(cd "$(dirname "$0")" && pwd)"
COMPOSE_FILE="$BASE_DIR/images/openim-server/docker-compose.build.yml"
RELEASE="${RELEASE:-false}"
PUSH="${PUSH:-false}"
DRY_RUN="${DRY_RUN:-false}"
PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}"
IMAGE_TAGS="${IMAGE_TAGS:-}"
IMAGE_REGISTRIES="${IMAGE_REGISTRIES:-}"
SERVICE="${SERVICE:-}"
if [[ ! -f "$COMPOSE_FILE" ]]; then
echo -e "${RED}docker-compose.build.yml not found: $COMPOSE_FILE${NO_COLOR}"
exit 1
fi
if [[ -z "$SERVICE" ]]; then
echo -e "${RED}SERVICE is required${NO_COLOR}"
exit 1
fi
cd "$BASE_DIR/.." || exit 1
split_values() {
echo "$1" | grep -o '[^, ]\+'
}
run_or_print() {
if [[ "$DRY_RUN" == "true" ]]; then
printf '%q ' "$@"
printf '\n'
else
"$@"
fi
}
if ! command -v jq >/dev/null 2>&1; then
echo -e "${RED}jq is required${NO_COLOR}"
exit 1
fi
if [[ -z "$IMAGE_TAGS" || -z "$IMAGE_REGISTRIES" ]]; then
echo -e "${RED}IMAGE_TAGS and IMAGE_REGISTRIES are required${NO_COLOR}"
exit 1
fi
image_tags=()
while IFS= read -r tag; do
image_tags+=("$tag")
done < <(split_values "$IMAGE_TAGS")
image_registries=()
while IFS= read -r registry; do
image_registries+=("$registry")
done < <(split_values "$IMAGE_REGISTRIES")
if [[ ${#image_tags[@]} -eq 0 || ${#image_registries[@]} -eq 0 ]]; then
echo -e "${RED}IMAGE_TAGS and IMAGE_REGISTRIES must contain at least one value${NO_COLOR}"
exit 1
fi
compose_config=$(docker compose -f "$COMPOSE_FILE" config --format json)
context=$(jq -r --arg service "$SERVICE" '.services[$service].build.context // empty' <<< "$compose_config")
dockerfile=$(jq -r --arg service "$SERVICE" '.services[$service].build.dockerfile // empty' <<< "$compose_config")
cmd_path=$(jq -r --arg service "$SERVICE" '.services[$service].build.args.CMD_PATH // empty' <<< "$compose_config")
binary_name=$(jq -r --arg service "$SERVICE" '.services[$service].build.args.BINARY_NAME // empty' <<< "$compose_config")
if [[ -z "$context" || -z "$dockerfile" || -z "$cmd_path" || -z "$binary_name" ]]; then
echo -e "${RED}Invalid build config for $SERVICE${NO_COLOR}"
exit 1
fi
if [[ ! -d "$cmd_path" && ! -f "$cmd_path/main.go" ]]; then
echo -e "${CYAN}Skipping $SERVICE because $cmd_path does not exist${NO_COLOR}"
exit 0
fi
tag_args=()
for registry in "${image_registries[@]}"; do
for tag in "${image_tags[@]}"; do
tag_args+=(--tag "$registry/$binary_name:$tag")
done
done
echo -e "${CYAN}Building $binary_name for $PLATFORMS...${NO_COLOR}"
run_or_print docker buildx build \
--platform "$PLATFORMS" \
--file "$dockerfile" \
--build-arg "CMD_PATH=$cmd_path" \
--build-arg "BINARY_NAME=$binary_name" \
--build-arg "RELEASE=$RELEASE" \
"${tag_args[@]}" \
--push \
"$context"
echo -e "${GREEN}Successfully pushed $binary_name${NO_COLOR}"
Loading…
Cancel
Save