diff --git a/.github/workflows/docker-build-and-release-services-images.yml b/.github/workflows/docker-build-and-release-services-images.yml index 941f83cfa..27b22be31 100644 --- a/.github/workflows/docker-build-and-release-services-images.yml +++ b/.github/workflows/docker-build-and-release-services-images.yml @@ -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</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}`); - } diff --git a/build/build_single.sh b/build/build_single.sh new file mode 100755 index 000000000..f5b1e8075 --- /dev/null +++ b/build/build_single.sh @@ -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}" diff --git a/go.mod b/go.mod index 4caa277da..c08218c92 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/openimsdk/protocol v0.0.73-alpha.12 + github.com/openimsdk/protocol v0.0.73-alpha.19 github.com/openimsdk/tools v0.0.50-alpha.119 github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 @@ -134,7 +134,7 @@ require ( github.com/lestrrat-go/strftime v1.0.6 // indirect github.com/lithammer/shortuuid v3.0.0+incompatible // indirect github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect - github.com/magefile/mage v1.16.1 // indirect + github.com/magefile/mage v1.17.2 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 47059e30d..e6bda0f88 100644 --- a/go.sum +++ b/go.sum @@ -319,6 +319,8 @@ github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6 github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/magefile/mage v1.16.1 h1:j5UwkdA48xTlGs0Hcm1Q3sSAcxBorntQjiewDNMsqlo= github.com/magefile/mage v1.16.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40= +github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -363,6 +365,8 @@ github.com/openimsdk/gomake v0.0.17 h1:q8haP48VOH45WhJRiLj1YSBJyUFJqD8CTedH65i1Y github.com/openimsdk/gomake v0.0.17/go.mod h1:nnjS8yCtrPJAt1knMbyPiUwCH2gpyBzj/EZAONfUOXg= github.com/openimsdk/protocol v0.0.73-alpha.12 h1:2NYawXeHChYUeSme6QJ9pOLh+Empce2WmwEtbP4JvKk= github.com/openimsdk/protocol v0.0.73-alpha.12/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw= +github.com/openimsdk/protocol v0.0.73-alpha.19 h1:CvXoDF2U73UcMhLnrtMFks2Aw+bXiDgH8AITEt783/s= +github.com/openimsdk/protocol v0.0.73-alpha.19/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw= github.com/openimsdk/tools v0.0.50-alpha.117 h1:ACfijEVCeBcttT7OOkNGOOOvq14pJtb9szNIMHLm6Vc= github.com/openimsdk/tools v0.0.50-alpha.117/go.mod h1:I0WESSa7ghPIo9BL+ETlH/qEIbO6+KZioM1jwNuDwz0= github.com/openimsdk/tools v0.0.50-alpha.119 h1:S/RjRtL0ciwiG6pZKssbU//qVSWi7AuKHOPCHsQgz68=