name: Release builder images on: push: branches: - main paths: - rust-toolchain.toml - 'docker/dev-builder/**' - '.github/runner-scale-sets/query-regression/**' workflow_dispatch: # Allows you to run this workflow manually. inputs: release_dev_builder_ubuntu_image: type: boolean description: Release dev-builder-ubuntu image required: false default: false release_dev_builder_centos_image: type: boolean description: Release dev-builder-centos image required: false default: false release_dev_builder_android_image: type: boolean description: Release dev-builder-android image required: false default: false release_dev_builder_riscv64_image: type: boolean description: Release dev-builder-riscv64 image required: false default: false release_query_regression_runner_image: type: boolean description: Rebuild the query-regression ECS runner image (and update QUERY_REGRESSION_ECS_IMAGE_ID + RUNNER_IMAGE_EPOCH) required: false default: false update_dev_builder_image_tag: type: boolean description: Update the DEV_BUILDER_IMAGE_TAG in Makefile and create a PR required: false default: false jobs: changes: name: Detect changed components runs-on: ubuntu-latest outputs: dev-builder: ${{ steps.filter.outputs.dev-builder }} query-regression-runner: ${{ steps.filter.outputs.query-regression-runner }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - name: Filter changes id: filter shell: bash run: | set -euo pipefail base="${{ github.event.before }}" head="${{ github.sha }}" if [[ "${{ github.event_name }}" != "push" ]] || [[ -z "${base}" ]] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then # Dispatch, or an unknown base (new branch/force-push): treat # everything as changed so gating falls back to job inputs. echo "dev-builder=true" >> "$GITHUB_OUTPUT" echo "query-regression-runner=true" >> "$GITHUB_OUTPUT" exit 0 fi files="$(git diff --name-only "${base}" "${head}")" dev_builder=false query_regression_runner=false # rust-toolchain.toml only gates the dev-builder images: they bake # the workspace toolchain. The query-regression runner toolchain is # pinned inside its Dockerfile (nightly-2026-03-21) and must NOT # follow the workspace pin, so its rebuild triggers only on changes # under its own directory. if grep -qx 'rust-toolchain.toml' <<<"${files}"; then dev_builder=true fi if grep -q '^docker/dev-builder/' <<<"${files}"; then dev_builder=true fi if grep -q '^\.github/runner-scale-sets/query-regression/' <<<"${files}"; then query_regression_runner=true fi echo "dev-builder=${dev_builder}" >> "$GITHUB_OUTPUT" echo "query-regression-runner=${query_regression_runner}" >> "$GITHUB_OUTPUT" release-dev-builder-images: name: Release dev builder images # The jobs are triggered by the following events: # 1. Manually triggered workflow_dispatch event # 2. Push event when the PR that modifies the `rust-toolchain.toml` or `docker/dev-builder/**` is merged to main needs: [changes] if: ${{ (github.event_name == 'push' && needs.changes.outputs.dev-builder == 'true') || inputs.release_dev_builder_ubuntu_image || inputs.release_dev_builder_centos_image || inputs.release_dev_builder_android_image || inputs.release_dev_builder_riscv64_image }} runs-on: ubuntu-latest outputs: version: ${{ steps.set-version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - name: Configure build image version id: set-version shell: bash run: | commitShortSHA=`echo ${{ github.sha }} | cut -c1-8` buildTime=`date +%Y%m%d%H%M%S` BUILD_VERSION="$commitShortSHA-$buildTime" # Extract the full channel string (e.g. "1.96.1" or # "nightly-2026-03-21") so the image version always carries the # toolchain it was built with. RUST_TOOLCHAIN_VERSION=$(grep -E '^channel' rust-toolchain.toml | cut -d'"' -f2) IMAGE_VERSION="${RUST_TOOLCHAIN_VERSION}-${BUILD_VERSION}" echo "VERSION=${IMAGE_VERSION}" >> $GITHUB_ENV echo "version=$IMAGE_VERSION" >> $GITHUB_OUTPUT - name: Build and push dev builder images uses: ./.github/actions/build-dev-builder-images with: version: ${{ env.VERSION }} dockerhub-image-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} dockerhub-image-registry-token: ${{ secrets.DOCKERHUB_TOKEN }} build-dev-builder-ubuntu: ${{ inputs.release_dev_builder_ubuntu_image || github.event_name == 'push' }} build-dev-builder-centos: ${{ inputs.release_dev_builder_centos_image || github.event_name == 'push' }} build-dev-builder-android: ${{ inputs.release_dev_builder_android_image || github.event_name == 'push' }} build-dev-builder-riscv64: ${{ inputs.release_dev_builder_riscv64_image || github.event_name == 'push' }} release-dev-builder-images-ecr: name: Release dev builder images to AWS ECR runs-on: ubuntu-latest needs: [ release-dev-builder-images ] steps: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ECR_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_ECR_SECRET_ACCESS_KEY }} aws-region: ${{ vars.ECR_REGION }} - name: Login to Amazon ECR id: login-ecr-public uses: aws-actions/amazon-ecr-login@v2 env: AWS_REGION: ${{ vars.ECR_REGION }} with: registry-type: public - name: Push dev-builder-ubuntu image shell: bash if: ${{ inputs.release_dev_builder_ubuntu_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ECR_IMAGE_REGISTRY: ${{ vars.ECR_IMAGE_REGISTRY }} ECR_IMAGE_NAMESPACE: ${{ vars.ECR_IMAGE_NAMESPACE }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-ubuntu:$IMAGE_VERSION \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-ubuntu:$IMAGE_VERSION docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-ubuntu:latest \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-ubuntu:latest - name: Push dev-builder-centos image shell: bash if: ${{ inputs.release_dev_builder_centos_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ECR_IMAGE_REGISTRY: ${{ vars.ECR_IMAGE_REGISTRY }} ECR_IMAGE_NAMESPACE: ${{ vars.ECR_IMAGE_NAMESPACE }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-centos:$IMAGE_VERSION \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-centos:$IMAGE_VERSION docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-centos:latest \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-centos:latest - name: Push dev-builder-android image shell: bash if: ${{ inputs.release_dev_builder_android_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ECR_IMAGE_REGISTRY: ${{ vars.ECR_IMAGE_REGISTRY }} ECR_IMAGE_NAMESPACE: ${{ vars.ECR_IMAGE_NAMESPACE }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-android:$IMAGE_VERSION \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-android:$IMAGE_VERSION docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-android:latest \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-android:latest - name: Push dev-builder-riscv64 image shell: bash if: ${{ inputs.release_dev_builder_riscv64_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ECR_IMAGE_REGISTRY: ${{ vars.ECR_IMAGE_REGISTRY }} ECR_IMAGE_NAMESPACE: ${{ vars.ECR_IMAGE_NAMESPACE }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-riscv64:$IMAGE_VERSION \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-riscv64:$IMAGE_VERSION - name: Push latest dev-builder-riscv64 image shell: bash if: ${{ github.event_name == 'push' }} env: IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ECR_IMAGE_REGISTRY: ${{ vars.ECR_IMAGE_REGISTRY }} ECR_IMAGE_NAMESPACE: ${{ vars.ECR_IMAGE_NAMESPACE }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-riscv64:latest \ docker://$ECR_IMAGE_REGISTRY/$ECR_IMAGE_NAMESPACE/dev-builder-riscv64:latest release-dev-builder-images-cn: # Note: Be careful issue: https://github.com/containers/skopeo/issues/1874 and we decide to use the latest stable skopeo container. name: Release dev builder images to CN region runs-on: ubuntu-latest needs: [ release-dev-builder-images ] steps: - name: Login to AliCloud Container Registry uses: docker/login-action@v3 with: registry: ${{ vars.ACR_IMAGE_REGISTRY }} username: ${{ secrets.ALICLOUD_USERNAME }} password: ${{ secrets.ALICLOUD_PASSWORD }} - name: Push dev-builder-ubuntu image shell: bash if: ${{ inputs.release_dev_builder_ubuntu_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ACR_IMAGE_REGISTRY: ${{ vars.ACR_IMAGE_REGISTRY }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-ubuntu:$IMAGE_VERSION \ docker://$ACR_IMAGE_REGISTRY/$IMAGE_NAMESPACE/dev-builder-ubuntu:$IMAGE_VERSION - name: Push dev-builder-centos image shell: bash if: ${{ inputs.release_dev_builder_centos_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ACR_IMAGE_REGISTRY: ${{ vars.ACR_IMAGE_REGISTRY }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-centos:$IMAGE_VERSION \ docker://$ACR_IMAGE_REGISTRY/$IMAGE_NAMESPACE/dev-builder-centos:$IMAGE_VERSION - name: Push dev-builder-android image shell: bash if: ${{ inputs.release_dev_builder_android_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ACR_IMAGE_REGISTRY: ${{ vars.ACR_IMAGE_REGISTRY }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-android:$IMAGE_VERSION \ docker://$ACR_IMAGE_REGISTRY/$IMAGE_NAMESPACE/dev-builder-android:$IMAGE_VERSION - name: Push dev-builder-riscv64 image shell: bash if: ${{ inputs.release_dev_builder_riscv64_image || github.event_name == 'push' }} env: IMAGE_VERSION: ${{ needs.release-dev-builder-images.outputs.version }} IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE }} ACR_IMAGE_REGISTRY: ${{ vars.ACR_IMAGE_REGISTRY }} run: | docker run -v "${DOCKER_CONFIG:-$HOME/.docker}:/root/.docker:ro" \ -e "REGISTRY_AUTH_FILE=/root/.docker/config.json" \ quay.io/skopeo/stable:latest \ copy -a docker://docker.io/$IMAGE_NAMESPACE/dev-builder-riscv64:$IMAGE_VERSION \ docker://$ACR_IMAGE_REGISTRY/$IMAGE_NAMESPACE/dev-builder-riscv64:$IMAGE_VERSION update-dev-builder-image-tag: name: Update dev-builder image tag runs-on: ubuntu-latest permissions: contents: write pull-requests: write if: ${{ github.event_name == 'push' || inputs.update_dev_builder_image_tag }} needs: [ release-dev-builder-images ] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Update dev-builder image tag shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | ./.github/scripts/update-dev-builder-version.sh ${{ needs.release-dev-builder-images.outputs.version }} rebuild-query-regression-runner-image: # Rebuilds the query-regression ECS runner image via the ops tool in # .github/runner-scale-sets/query-regression/ecs-image/: boots a # temporary pay-as-you-go ECS builder, snapshots a new custom image, # then completes the documented lockstep updates: # 1. bumps RUNNER_IMAGE_EPOCH in query-regression.yml (target-cache # invalidation) via a PR from a timestamped ci/ branch (main is # branch-protected; mirrors update-dev-builder-version.sh), and # 2. points the repo variable QUERY_REGRESSION_ECS_IMAGE_ID at the # new image. The runner toolchain is pinned inside the Dockerfile # (nightly-2026-03-21, independent of rust-toolchain.toml — the # benchmark must compile historical base checkouts), so image # rebuilds do not change the cache ABI. # # Required repository configuration (same names the provisioning job # uses): vars ALIYUN_ECS_REGION_ID, ALIYUN_ECS_VSWITCH_ID, # ALIYUN_ECS_SECURITY_GROUP_ID, optionally ALIYUN_ECS_RESOURCE_GROUP_ID # and ALIYUN_ECS_BASE_IMAGE_ID (deterministic base-image pin; otherwise # the rebuild auto-resolves the latest public Ubuntu 26.04 image). # Secrets: ALICLOUD_ECS_ACCESS_KEY_ID, ALICLOUD_ECS_ACCESS_KEY_SECRET, # GH_PERSONAL_ACCESS_TOKEN (repo push + actions-variable write). name: Rebuild query-regression runner image needs: [changes] if: ${{ github.repository == 'GreptimeTeam/greptimedb' && ((github.event_name == 'push' && needs.changes.outputs.query-regression-runner == 'true') || inputs.release_query_regression_runner_image) }} runs-on: ubuntu-latest timeout-minutes: 150 steps: - name: Checkout uses: actions/checkout@v4 with: token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} # Keep the PAT credential for the epoch-bump push below. persist-credentials: true - name: Install uv uses: astral-sh/setup-uv@v6 - name: Rebuild the runner image id: rebuild env: ALIBABA_CLOUD_ACCESS_KEY_ID: ${{ secrets.ALICLOUD_ECS_ACCESS_KEY_ID }} ALIBABA_CLOUD_ACCESS_KEY_SECRET: ${{ secrets.ALICLOUD_ECS_ACCESS_KEY_SECRET }} run: | set -euo pipefail args=( --region-id "${{ vars.ALIYUN_ECS_REGION_ID }}" --vswitch-id "${{ vars.ALIYUN_ECS_VSWITCH_ID }}" --security-group-id "${{ vars.ALIYUN_ECS_SECURITY_GROUP_ID }}" --base-image-id "${{ vars.ALIYUN_ECS_BASE_IMAGE_ID }}" ) if [[ -n "${{ vars.ALIYUN_ECS_RESOURCE_GROUP_ID }}" ]]; then args+=(--resource-group-id "${{ vars.ALIYUN_ECS_RESOURCE_GROUP_ID }}") fi uv run .github/runner-scale-sets/query-regression/ecs-image/build-ecs-image.py \ "${args[@]}" 2>&1 | tee rebuild.log image_id="$(sed -n 's/^Custom image ready: \([^ ]*\) .*/\1/p' rebuild.log | tail -n1)" if [[ -z "${image_id}" ]]; then echo "Error: no image id found in build output" >&2 exit 1 fi echo "image_id=${image_id}" >> "$GITHUB_OUTPUT" echo "## Rebuilt runner image" >> "$GITHUB_STEP_SUMMARY" echo "New image: \`${image_id}\`" >> "$GITHUB_STEP_SUMMARY" - name: Open the epoch-bump PR and update the image id variable env: GH_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} run: | set -euo pipefail image_id="${{ steps.rebuild.outputs.image_id }}" old_epoch="$(grep -oE 'readonly RUNNER_IMAGE_EPOCH="[0-9]+"' \ .github/workflows/query-regression.yml | grep -oE '[0-9]+' | head -n1)" if [[ -z "${old_epoch}" ]]; then echo "Error: RUNNER_IMAGE_EPOCH not found in query-regression.yml" >&2 exit 1 fi new_epoch=$((old_epoch + 1)) # main is branch-protected (required reviews + status checks), so # the epoch bump cannot be pushed there directly. Mirror # .github/scripts/update-dev-builder-version.sh: timestamped bot # branch, plain push, and a PR with reviewers. Do NOT use [skip ci] # on the commit: the required checks must be able to run for the PR # to become mergeable. BRANCH="ci/update-query-regression-epoch-$(date +%Y%m%d%H%M%S)" git config user.name "greptimedb-ci" git config user.email "greptimedb-ci@greptime.com" git fetch origin main git checkout -b "${BRANCH}" origin/main sed -i "s/readonly RUNNER_IMAGE_EPOCH=\"${old_epoch}\"/readonly RUNNER_IMAGE_EPOCH=\"${new_epoch}\"/" \ .github/workflows/query-regression.yml git add .github/workflows/query-regression.yml git commit -s -m "ci(query-regression): bump RUNNER_IMAGE_EPOCH to ${new_epoch} for image ${image_id}" git push origin "${BRANCH}" gh pr create \ --title "ci(query-regression): bump RUNNER_IMAGE_EPOCH to ${new_epoch}" \ --body "Auto-generated by the \`Rebuild query-regression runner image\` job in ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} for image \`${image_id}\`. Merging this invalidates the query-regression target cache (RUNNER_IMAGE_EPOCH ${old_epoch} → ${new_epoch}). The \`QUERY_REGRESSION_ECS_IMAGE_ID\` repo variable already points at the new image; the epoch bump is belt-and-suspenders for image-content drift." \ --base main \ --head "${BRANCH}" \ --reviewer sunng87 \ --reviewer daviderli614 \ --reviewer killme2008 \ --reviewer evenyag \ --reviewer fengjiachun \ --reviewer WenyXu # Point the variable at the new image once the PR exists. Image # rebuilds keep the same pinned toolchain, so the cache ABI is # unchanged and not waiting for the PR merge is fine. gh api -X PATCH "repos/${{ github.repository }}/actions/variables/QUERY_REGRESSION_ECS_IMAGE_ID" \ -f value="${image_id}" echo "## Lockstep updates" >> "$GITHUB_STEP_SUMMARY" echo "- QUERY_REGRESSION_ECS_IMAGE_ID → \`${image_id}\`" >> "$GITHUB_STEP_SUMMARY" echo "- RUNNER_IMAGE_EPOCH → \`${new_epoch}\` (PR on \`${BRANCH}\`, pending review/merge)" >> "$GITHUB_STEP_SUMMARY"