Skip to content
56 changes: 46 additions & 10 deletions .github/scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,74 @@
set -e # trigger failure on error - do not remove!
set -x # display command on output

## debug
# TARGET_VERSION="1.2.x"

if [ -z "${TARGET_VERSION}" ]; then
>&2 echo "No TARGET_VERSION specified"
exit 1
fi
CHGLOG_FILE="${CHGLOG_FILE:-CHANGELOG.md}"

# update package version
# Update package version
uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version "${TARGET_VERSION}"
uv lock --upgrade-package docling-serve

# collect release notes
# Extract all docling packages and versions from uv.lock
DOCVERSIONS=$(uvx --with toml python3 - <<'PY'
import toml
data = toml.load("uv.lock")
for pkg in data.get("package", []):
if pkg["name"].startswith("docling"):
print(f"{pkg['name']} {pkg['version']}")
PY
)

# Format docling versions list without trailing newline
DOCLING_VERSIONS="### Docling libraries included in this release:"
while IFS= read -r line; do
DOCLING_VERSIONS+="
- $line"
done <<< "$DOCVERSIONS"

# Collect release notes
REL_NOTES=$(mktemp)
uv run --no-sync semantic-release changelog --unreleased >> "${REL_NOTES}"

# update changelog
# Strip trailing blank lines from release notes and append docling versions
{
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' "${REL_NOTES}"
printf "\n"
printf "%s" "${DOCLING_VERSIONS}"
printf "\n"
} > "${REL_NOTES}.tmp" && mv "${REL_NOTES}.tmp" "${REL_NOTES}"

# Update changelog
TMP_CHGLOG=$(mktemp)
TARGET_TAG_NAME="v${TARGET_VERSION}"
RELEASE_URL="$(gh repo view --json url -q ".url")/releases/tag/${TARGET_TAG_NAME}"
printf "## [${TARGET_TAG_NAME}](${RELEASE_URL}) - $(date -Idate)\n\n" >> "${TMP_CHGLOG}"
cat "${REL_NOTES}" >> "${TMP_CHGLOG}"
if [ -f "${CHGLOG_FILE}" ]; then
printf "\n" | cat - "${CHGLOG_FILE}" >> "${TMP_CHGLOG}"
fi
## debug
#RELEASE_URL="myrepo/releases/tag/${TARGET_TAG_NAME}"

# Strip leading blank lines from existing changelog to avoid multiple blank lines when appending
EXISTING_CL=$(sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' "${CHGLOG_FILE}")

{
printf "## [${TARGET_TAG_NAME}](${RELEASE_URL}) - $(date -Idate)\n\n"
cat "${REL_NOTES}"
printf "\n"
printf "%s\n" "${EXISTING_CL}"
} >> "${TMP_CHGLOG}"

mv "${TMP_CHGLOG}" "${CHGLOG_FILE}"

# push changes
# Push changes
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add pyproject.toml uv.lock "${CHGLOG_FILE}"
COMMIT_MSG="chore: bump version to ${TARGET_VERSION} [skip ci]"
git commit -m "${COMMIT_MSG}"
git push origin main

# create GitHub release (incl. Git tag)
# Create GitHub release (incl. Git tag)
gh release create "${TARGET_TAG_NAME}" -F "${REL_NOTES}"
204 changes: 201 additions & 3 deletions .github/workflows/job-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
push: ${{ inputs.publish }}
push: ${{ inputs.publish }} # set false for local test
tags: ${{ steps.ghcr_meta.outputs.tags }}
labels: ${{ steps.ghcr_meta.outputs.labels }}
platforms: ${{ inputs.platforms}}
Expand Down Expand Up @@ -123,19 +123,217 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
push: ${{ inputs.publish }}
push: ${{ inputs.publish }} # set false for local test
tags: ${{ steps.quay_meta.outputs.tags }}
labels: ${{ steps.quay_meta.outputs.labels }}
platforms: ${{ inputs.platforms}}
cache-from: type=gha
cache-to: type=gha,mode=max
file: Containerfile
build-args: ${{ inputs.build_args }}

# - name: Inspect the image details
# run: |
# echo "${{ steps.ghcr_push.outputs.metadata }}"

- name: Remove Local Docker Images
run: |
docker image prune -af

# - name: Set metadata outputs for local testing, and comment out buildx, cache, metadata
# id: ghcr_meta
# run: |
# echo "tags=ghcr.io/docling-project/docling-serve-cpu:main" >> $GITHUB_OUTPUT
# echo "labels=org.opencontainers.image.source=https://github.com/docling-project/docling-serve" >> $GITHUB_OUTPUT

outputs:
image-tags: ${{ steps.ghcr_meta.outputs.tags }}
image-labels: ${{ steps.ghcr_meta.outputs.labels }}

test-cpu-image:
needs:
- image
runs-on: ubuntu-latest
permissions:
contents: read
packages: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Test CPU images
run: |
set -e

echo "Testing image: ${{ needs.image.outputs.image-tags }}"

for tag in ${{ needs.image.outputs.image-tags }}; do
if echo "$tag" | grep -q -- '-cpu' && echo "$tag" | grep -qE ':[vV][0-9]+(\.[0-9]+){0,2}$'; then
echo "Testing CPU image: $tag"

# Remove existing container if any
docker rm -f docling-serve-test-container 2>/dev/null || true

echo "Pulling image..."
docker pull "$tag"

echo "Waiting 5s after pull..."
sleep 5

echo "Starting container..."
docker run -d -p 5001:5001 --name docling-serve-test-container "$tag"

echo "Waiting 15s for container to boot..."
sleep 15

echo "Checking service health..."
for i in {1..20}; do
health_response=$(curl -s http://localhost:5001/health || true)
echo "Health check response [$i]: $health_response"
if echo "$health_response" | grep -q '"status":"ok"'; then
echo "Service is healthy!"
echo "Sending test conversion request..."

status_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST 'http://localhost:5001/v1/convert/source' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"options": {
"from_formats": ["pdf"],
"to_formats": ["md"]
},
"sources": [
{
"kind": "http",
"url": "https://arxiv.org/pdf/2501.17887"
}
],
"target": {
"kind": "inbody"
}
}')

echo "Conversion request returned status code: $status_code"

if [ "$status_code" -ne 200 ]; then
echo "Conversion failed!"
docker logs docling-serve-test-container
docker rm -f docling-serve-test-container
exit 1
fi

break
else
echo "Waiting for service... [$i/20]"
sleep 3
fi
done

if ! echo "$health_response" | grep -q '"status":"ok"'; then
echo "Service did not become healthy in time."
docker logs docling-serve-test-container
docker rm -f docling-serve-test-container
exit 1
fi

echo "Cleaning up test container..."
docker rm -f docling-serve-test-container
else
echo "Skipping non-released or non-CPU image: $tag"
fi
done

test-cuda-image:
needs:
- image
runs-on: ubuntu-latest # >> placeholder for GPU runner << #
permissions:
contents: read
packages: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Test CUDA images
run: |
set -e

echo "Testing image: ${{ needs.image.outputs.image-tags }}"

for tag in ${{ needs.image.outputs.image-tags }}; do
if echo "$tag" | grep -qE -- '-cu[0-9]+' && echo "$tag" | grep -qE ':[vV][0-9]+(\.[0-9]+){0,2}$'; then
echo "Testing CUDA image: $tag"

# Remove existing container if any
docker rm -f docling-serve-test-container 2>/dev/null || true

echo "Pulling image..."
docker pull "$tag"

echo "Waiting 5s after pull..."
sleep 5

echo "Starting container..."
docker run -d -p 5001:5001 --gpus all --name docling-serve-test-container "$tag"

echo "Waiting 15s for container to boot..."
sleep 15

echo "Checking service health..."
for i in {1..25}; do
health_response=$(curl -s http://localhost:5001/health || true)
echo "Health check response [$i]: $health_response"
if echo "$health_response" | grep -q '"status":"ok"'; then
echo "Service is healthy!"
echo "Sending test conversion request..."

status_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST 'http://localhost:5001/v1/convert/source' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"options": {
"from_formats": ["pdf"],
"to_formats": ["md"]
},
"sources": [
{
"kind": "http",
"url": "https://arxiv.org/pdf/2501.17887"
}
],
"target": {
"kind": "inbody"
}
}')

echo "Conversion request returned status code: $status_code"

if [ "$status_code" -ne 200 ]; then
echo "Conversion failed!"
docker logs docling-serve-test-container
docker rm -f docling-serve-test-container
exit 1
fi

break
else
echo "Waiting for service... [$i/25]"
sleep 3
fi
done

if ! echo "$health_response" | grep -q '"status":"ok"'; then
echo "Service did not become healthy in time."
docker logs docling-serve-test-container
docker rm -f docling-serve-test-container
exit 1
fi

echo "Cleaning up test container..."
docker rm -f docling-serve-test-container
else
echo "Skipping non-released or non-CUDA image: $tag"
fi
done
Loading