name: "Docker release promote"
|
|
on:
|
workflow_dispatch:
|
inputs:
|
release_tag:
|
description: "Release tag for promotion"
|
required: true
|
type: string
|
workflow_call:
|
inputs:
|
release_tag:
|
required: true
|
type: string
|
|
env:
|
DOCKER_IMAGE_TAG_CHECK_NAME: "Docker image tag"
|
IMAGE_NAME: "${{ vars.DOCKERHUB_ORG }}/label-studio"
|
RELEASE_DOCKERFILE: "Dockerfile.release"
|
LAUNCHDARKLY_DOWNLOAD_PATH: "feature_flags.json"
|
|
jobs:
|
docker_release_retag:
|
name: "Docker image (${{ matrix.platform }})"
|
timeout-minutes: 90
|
runs-on: ${{ matrix.runner }}
|
strategy:
|
fail-fast: false
|
matrix:
|
include:
|
- platform: linux/amd64
|
runner: ubuntu-latest
|
- platform: linux/arm64
|
runner: ubuntu-24.04-arm
|
outputs:
|
image_version: ${{ steps.get_info.outputs.image_version }}
|
ubuntu_tags: ${{ steps.generate-tags.outputs.ubuntu-tags }}
|
sha: ${{ steps.get_info.outputs.sha }}
|
steps:
|
- uses: hmarr/debug-action@v3.0.0
|
|
- name: Get an artifact from check suite
|
uses: actions/github-script@v8
|
id: get_info
|
with:
|
github-token: ${{ secrets.GIT_PAT }}
|
script: |
|
const {repo, owner} = context.repo;
|
const check_runs = await github.paginate(
|
github.rest.checks.listForRef,
|
{
|
owner,
|
repo,
|
ref: 'tags/${{ inputs.release_tag }}',
|
status: "completed",
|
per_page: 100
|
},
|
(response) => response.data
|
);
|
const check = check_runs.find(e => e.name === '${{ env.DOCKER_IMAGE_TAG_CHECK_NAME }}')
|
const details = JSON.parse(check.output.summary)
|
console.log(details)
|
core.setOutput("branch", details.branch);
|
core.setOutput("pretty_branch_name", details.pretty_branch_name);
|
core.setOutput("image_version", details.image_version);
|
core.setOutput("sha", details.sha);
|
|
- name: Checkout
|
uses: actions/checkout@v6
|
with:
|
ref: ${{ inputs.release_tag }}
|
|
- name: Check if the latest tag needs to be updated
|
uses: actions/github-script@v8
|
id: generate-tags
|
with:
|
github-token: ${{ secrets.GIT_PAT }}
|
script: |
|
const {repo, owner} = context.repo;
|
const newTag = '${{ inputs.release_tag }}';
|
const regexp = '^[v]?([0-9]+)\.([0-9]+)\.([0-9]+)(\.post([0-9]+))?$';
|
|
function compareVersions(a, b) {
|
if (a[1] === b[1])
|
if (a[2] === b[2])
|
if (a[3] === b[3])
|
return (+a[5] || -1) - (+b[5] || -1)
|
else
|
return +a[3] - b[3]
|
else
|
return +a[2] - b[2]
|
else
|
return +a[1] - b[1]
|
}
|
|
const tags = await github.paginate(
|
github.rest.repos.listTags,
|
{
|
owner,
|
repo,
|
per_page: 100
|
},
|
(response) => response.data
|
);
|
const rawTags = tags.map(e => e.name)
|
const filteredTags = rawTags.filter(e => e.match(regexp))
|
const sortedTags = filteredTags
|
.map(e => e.match(regexp))
|
.sort(compareVersions)
|
.reverse()
|
console.log('Sorted tags:')
|
console.log(sortedTags.map(e => e[0]))
|
|
const newestVersion = sortedTags[0];
|
console.log(`Newest tag: ${newestVersion[0]}`)
|
|
let dockerHubUbuntuRawTags = [newTag];
|
|
if (compareVersions(newTag.match(regexp), newestVersion) >= 0) {
|
console.log(`new tag ${newTag} is higher that all existing tags`)
|
console.log(dockerHubUbuntuRawTags)
|
dockerHubUbuntuRawTags.push('latest')
|
core.setOutput("latest", true);
|
} else {
|
console.log('not latest')
|
core.setOutput("latest", false);
|
}
|
|
const ubuntuTags = dockerHubUbuntuRawTags.join("\n");
|
console.log('Ubuntu tags:')
|
console.log(ubuntuTags)
|
|
core.setOutput("ubuntu-tags", ubuntuTags);
|
|
- name: Set up Python
|
uses: actions/setup-python@v6
|
with:
|
python-version: '3.13'
|
|
- name: Create version_.py
|
run: |
|
python3 $(pwd)/label_studio/core/version.py
|
cat $(pwd)/label_studio/core/version_.py
|
|
- name: Set up Docker Buildx
|
uses: docker/setup-buildx-action@v3.11.1
|
|
- name: Login to DockerHub
|
uses: docker/login-action@v3.5.0
|
with:
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
- name: Prepare
|
run: |
|
platform=${{ matrix.platform }}
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
- name: Prepare Release Dockerfile
|
id: release_dockerfile
|
env:
|
VERSION_OVERRIDE: ${{ inputs.release_tag }}
|
BRANCH_OVERRIDE: ${{ steps.get_info.outputs.branch }}
|
shell: bash
|
run: |
|
set -euo pipefail ${ACTIONS_STEP_DEBUG:+-x}
|
|
release_dir=release_${{ inputs.release_tag }}
|
echo "release_dir=$release_dir" >> $GITHUB_OUTPUT
|
|
mkdir -p $release_dir
|
cp label_studio/core/version_.py $release_dir/
|
|
cd $release_dir
|
|
cat <<EOF > "${{ env.RELEASE_DOCKERFILE }}"
|
FROM ${{ env.IMAGE_NAME }}:${{ steps.get_info.outputs.image_version }}
|
COPY --chown=54546:0 version_.py /label-studio/label_studio/core/version_.py
|
EOF
|
|
- name: Extract Docker metadata
|
id: meta
|
uses: docker/metadata-action@v5
|
with:
|
images: ${{ env.IMAGE_NAME }}
|
|
- name: Build and Push Release Ubuntu Docker image (${{ matrix.platform }})
|
uses: docker/build-push-action@v6.18.0
|
id: docker_build
|
with:
|
context: ${{ steps.release_dockerfile.outputs.release_dir }}
|
file: ${{ steps.release_dockerfile.outputs.release_dir }}/${{ env.RELEASE_DOCKERFILE }}
|
sbom: true
|
provenance: true
|
tags: ${{ env.IMAGE_NAME }}
|
labels: ${{ steps.meta.outputs.labels }}
|
cache-from: type=gha
|
cache-to: type=gha,mode=max
|
platforms: ${{ matrix.platform }}
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
|
- name: Export digest
|
run: |
|
mkdir -p ${{ runner.temp }}/digests
|
digest="${{ steps.docker_build.outputs.digest }}"
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
|
- name: Upload digest
|
uses: actions/upload-artifact@v6
|
with:
|
name: digests-${{ env.PLATFORM_PAIR }}
|
path: ${{ runner.temp }}/digests/*
|
if-no-files-found: error
|
retention-days: 1
|
|
merge_docker_manifest:
|
runs-on: ubuntu-latest
|
needs:
|
- docker_release_retag
|
steps:
|
- name: Download digests
|
uses: actions/download-artifact@v7
|
with:
|
path: ${{ runner.temp }}/digests
|
pattern: digests-*
|
merge-multiple: true
|
|
- name: Login to Docker Hub
|
uses: docker/login-action@v3
|
with:
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
- name: Set up Docker Buildx
|
uses: docker/setup-buildx-action@v3
|
|
- name: Extract Docker metadata
|
id: meta
|
uses: docker/metadata-action@v5
|
with:
|
images: ${{ env.IMAGE_NAME }}
|
labels: |
|
org.opencontainers.image.revision=${{ needs.docker_release_retag.outputs.sha }}
|
tags: ${{ needs.docker_release_retag.outputs.ubuntu_tags }}
|
|
- name: Create manifest list and push
|
working-directory: ${{ runner.temp }}/digests
|
run: |
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
|
- name: Inspect image
|
run: |
|
docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
|
copy_static_files:
|
name: "Copy compiled static from built Docker image"
|
runs-on: ubuntu-latest
|
needs: [docker_release_retag, merge_docker_manifest]
|
steps:
|
- name: Copy compiled static from builded Docker image
|
run: |
|
# Usually it takes 10-20 sec so the image becomes available
|
sleep 10s
|
docker pull ${{ env.IMAGE_NAME }}:${{ inputs.release_tag }}
|
docker run -v ${{ github.workspace }}:/workspace:rw -u root --rm ${{ env.IMAGE_NAME }}:${{ inputs.release_tag }} cp -r /label-studio/web/dist/ /workspace/web/
|
|
- name: Create Sentry release @ backend
|
uses: getsentry/action-release@v1
|
continue-on-error: true
|
env:
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
with:
|
version: label-studio@${{ inputs.release_tag }}
|
projects: opensource-v1-backend
|
|
- name: Create Sentry release @ frontend
|
uses: getsentry/action-release@v1
|
continue-on-error: true
|
env:
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
with:
|
version: label-studio@${{ inputs.release_tag }}
|
projects: opensource-v1-frontend
|
sourcemaps: web/dist
|