name: Docker Images on: push: tags: - v* # Manual runs validate the complete build without publishing mutable tags. # To republish a release, run the workflow against its existing v* tag. workflow_dispatch: permissions: contents: read concurrency: group: docker-image-${{ github.ref }} cancel-in-progress: false env: PROFILE: pro DOCKER_REGISTRY_DEFAULT: pnoker DOCKER_REGISTRY_ALIYUN: registry.cn-beijing.aliyuncs.com/dc3 jobs: validate: runs-on: ubuntu-latest timeout-minutes: 10 outputs: publish: ${{ steps.variables.outputs.publish }} service_version: ${{ steps.variables.outputs.service_version }} service_series: ${{ steps.variables.outputs.service_series }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Validate release identity id: variables run: | set -euo pipefail VERSION=$(awk -F'[<>]' '//,/<\/parent>/{if($2=="version") print $3}' pom.xml | head -1) if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Invalid project version in pom.xml: $VERSION" >&2 exit 1 fi SERIES="${VERSION%.*}" PUBLISH=false if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then EXPECTED_TAG="v$VERSION" if [[ "$GITHUB_REF_NAME" != "$EXPECTED_TAG" ]]; then echo "Release tag '$GITHUB_REF_NAME' does not match pom.xml version '$EXPECTED_TAG'." >&2 exit 1 fi PUBLISH=true fi echo "publish=$PUBLISH" >> "$GITHUB_OUTPUT" echo "service_version=$VERSION" >> "$GITHUB_OUTPUT" echo "service_series=$SERIES" >> "$GITHUB_OUTPUT" verify-backend: uses: ./.github/workflows/test.yml verify-web: uses: ./.github/workflows/ci-web.yml build-backend: needs: - validate - verify-backend - verify-web runs-on: ubuntu-latest timeout-minutes: 180 environment: release permissions: contents: read actions: write # for GitHub Actions cache (--cache-to type=gha) steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Set QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 - name: Set Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c - name: Login Docker Default Registry if: needs.validate.outputs.publish == 'true' uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login Docker Aliyun Registry if: needs.validate.outputs.publish == 'true' uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 with: registry: registry.cn-beijing.aliyuncs.com username: ${{ vars.ALIYUN_DOCKERHUB_USERNAME }} password: ${{ secrets.ALIYUN_DOCKERHUB_TOKEN }} - name: Build backend images env: PUBLISH: ${{ needs.validate.outputs.publish }} SERVICE_VERSION: ${{ needs.validate.outputs.service_version }} SERVICE_SERIES: ${{ needs.validate.outputs.service_series }} run: | set -euo pipefail services=( dc3-gateway dc3-center-agentic dc3-center-auth dc3-center-data dc3-center-manager dc3-center-single dc3-driver-listening-virtual dc3-driver-modbus-tcp dc3-driver-modbus-rtu dc3-driver-mqtt dc3-driver-opc-da dc3-driver-opc-ua dc3-driver-plcs7 dc3-driver-virtual dc3-driver-bacnet-ip dc3-driver-ble dc3-driver-can dc3-driver-coap dc3-driver-dlms dc3-driver-ethernet-ip dc3-driver-fins dc3-driver-http dc3-driver-iec104 dc3-driver-lwm2m dc3-driver-melsec dc3-driver-mysql dc3-driver-oracle dc3-driver-postgresql dc3-driver-serial dc3-driver-sl651 dc3-driver-snmp dc3-driver-sqlserver dc3-driver-tcp-udp dc3-driver-zigbee ) for name in "${services[@]}"; do output_args=(--output=type=cacheonly) tag_args=() if [[ "$PUBLISH" == "true" ]]; then output_args=(--push) tag_args=( --tag "$DOCKER_REGISTRY_DEFAULT/$name:latest" --tag "$DOCKER_REGISTRY_DEFAULT/$name:$SERVICE_SERIES" --tag "$DOCKER_REGISTRY_DEFAULT/$name:$SERVICE_VERSION" --tag "$DOCKER_REGISTRY_ALIYUN/$name:latest" --tag "$DOCKER_REGISTRY_ALIYUN/$name:$SERVICE_SERIES" --tag "$DOCKER_REGISTRY_ALIYUN/$name:$SERVICE_VERSION" ) fi echo "Building $name (publish=$PUBLISH)" # Single root-level Dockerfile defines a target stage per service. # The shared `builder` stage (mvn package) runs ONCE for the # whole reactor; BuildKit reuses it across every target image. # GHA cache (type=gha) persists the builder stage between runs so # subsequent CI invocations only rebuild what actually changed. docker buildx build \ --platform linux/arm64,linux/amd64 \ --file Dockerfile \ --target "$name" \ --build-arg PROFILE="$PROFILE" \ --cache-from type=gha,scope=dc3-builder \ --cache-to type=gha,scope=dc3-builder,mode=max \ "${output_args[@]}" \ "${tag_args[@]}" \ . done build-web: needs: - validate - verify-backend - verify-web runs-on: ubuntu-latest timeout-minutes: 60 environment: release permissions: contents: read actions: write steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Set QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 - name: Set Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c - name: Login DockerHub if: needs.validate.outputs.publish == 'true' uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login Aliyun Registry if: needs.validate.outputs.publish == 'true' uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 with: registry: registry.cn-beijing.aliyuncs.com username: ${{ vars.ALIYUN_DOCKERHUB_USERNAME }} password: ${{ secrets.ALIYUN_DOCKERHUB_TOKEN }} - name: Docker metadata id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 with: images: | pnoker/dc3-web registry.cn-beijing.aliyuncs.com/dc3/dc3-web tags: | type=match,pattern=v(.*),group=1 type=raw,value=latest - name: Build web image uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 with: context: . file: dc3-web/Dockerfile push: ${{ needs.validate.outputs.publish == 'true' }} platforms: linux/arm64,linux/amd64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=dc3-web cache-to: type=gha,scope=dc3-web,mode=max release: runs-on: ubuntu-latest needs: - build-backend - build-web - validate if: startsWith(github.ref, 'refs/tags/v') timeout-minutes: 20 environment: release permissions: contents: write steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with: fetch-depth: 0 - name: Generate release notes id: release_notes env: SERVICE_VERSION: ${{ needs.validate.outputs.service_version }} SERVICE_WORKSPACE: ${{ github.workspace }} run: | set -euo pipefail { echo "release_notes<> "$GITHUB_OUTPUT" - name: Create GitHub Release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 with: name: ${{ needs.validate.outputs.service_version }} body: ${{ steps.release_notes.outputs.release_notes }} tag_name: ${{ github.ref_name }} draft: false prerelease: false generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}