Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/cicd_1-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ jobs:
contents: read
packages: write

# Publish Test Image - for PRs labeled "PR: docker image", promote the
# docker-image artifact this build just produced to dotcms/dotcms-test, in
# parallel with the tests below. Label-gated and internal-branch only (fork PRs
# have no DOCKER_* secrets). Skipped automatically when build did not run.
publish-test-image:
name: Publish Test Image
needs: [ initialize, build ]
if: >-
always() && needs.build.result == 'success' &&
github.event.pull_request.head.repo.full_name == github.repository &&
contains(github.event.pull_request.labels.*.name, 'PR: docker image')
uses: ./.github/workflows/cicd_comp_publish-pr-test-image.yml
with:
pr: ${{ github.event.pull_request.number }}
sha: ${{ github.event.pull_request.head.sha }}
branch: ${{ github.event.pull_request.head.ref }}
artifact_run_id: ${{ github.run_id }}
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
permissions:
contents: read
actions: read
pull-requests: write

# Test job - runs various tests based on initialization outputs
test:
name: PR Test
Expand Down
164 changes: 164 additions & 0 deletions .github/workflows/cicd_comp_publish-pr-test-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Reusable: promote an already-built docker-image artifact to dotcms/dotcms-test.
#
# Does NOT build. The PR build (core-cicd/maven-job) already builds
# dotcms/dotcms-test:<version> and uploads it as the `docker-image` artifact;
# this loads that artifact, retags it for the PR, and pushes it to Docker Hub.
#
# Callers:
# cicd_1-pr.yml — needs: build, passes artifact_run_id = github.run_id
# cicd_publish-pr-test-image.yml (pull_request_target labeled) — omits
# artifact_run_id, so this workflow discovers the newest docker-image
# artifact for the head SHA.
#
# Tag scheme — branch feat/x on PR #1234 ->
# dotcms/dotcms-test:pr-1234-feat-x (mutable)
# dotcms/dotcms-test:pr-1234-feat-x_<sha7> (immutable)

name: 'Publish PR Test Image (reusable)'

on:
workflow_call:
inputs:
pr:
required: true
type: string
sha:
required: true
type: string
branch:
required: true
type: string
artifact_run_id:
description: 'Run id holding the docker-image artifact; empty = discover by SHA'
required: false
type: string
default: ''
secrets:
DOCKER_USERNAME:
required: true
DOCKER_TOKEN:
required: true

permissions:
contents: read

jobs:
publish:
name: Promote to Docker Hub
runs-on: ubuntu-${{ vars.UBUNTU_RUNNER_VERSION || '24.04' }}
permissions:
contents: read
actions: read
pull-requests: write
steps:
- name: Resolve artifact run and tag
id: resolve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
SHA: ${{ inputs.sha }}
BRANCH: ${{ inputs.branch }}
PR: ${{ inputs.pr }}
RUN_ID_IN: ${{ inputs.artifact_run_id }}
run: |
set -euo pipefail
run_id="${RUN_ID_IN}"
# Label path has no run id — find the newest run with a non-expired
# docker-image artifact for this exact commit.
if [ -z "${run_id}" ]; then
for rid in $(gh api "repos/${REPO}/actions/runs?head_sha=${SHA}&per_page=50" \
--jq '.workflow_runs[].id'); do
[ "${rid}" = "${GITHUB_RUN_ID}" ] && continue
if gh api "repos/${REPO}/actions/runs/${rid}/artifacts" \
--jq '.artifacts[] | select(.expired==false) | .name' \
| grep -qx 'docker-image'; then
run_id="${rid}"; break
fi
done
fi
if [ -z "${run_id}" ]; then
echo "No docker-image artifact for ${SHA}; nothing to publish yet."
echo "found=false" >> "$GITHUB_OUTPUT"; exit 0
fi
norm=$(printf '%s' "${BRANCH}" \
| tr '[:upper:]' '[:lower:]' \
| tr -c 'a-z0-9._-' '-' \
| sed -E 's/-+/-/g; s/^[-.]+//; s/[-.]+$//' \
| cut -c1-100)
{
echo "found=true"
echo "run_id=${run_id}"
echo "tag=pr-${PR}-${norm}"
} >> "$GITHUB_OUTPUT"
echo "Publishing pr-${PR}-${norm} from run ${run_id}"

- name: Download prebuilt image
if: steps.resolve.outputs.found == 'true'
uses: actions/download-artifact@v4
with:
name: docker-image
path: /tmp/prebuilt
run-id: ${{ steps.resolve.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Load, retag and push
id: push
if: steps.resolve.outputs.found == 'true'
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
TAG: ${{ steps.resolve.outputs.tag }}
SHA: ${{ inputs.sha }}
run: |
set -euo pipefail
DEST='dotcms/dotcms-test'
SHA7="${SHA:0:7}"

loaded=$(docker load -i /tmp/prebuilt/image.tar | sed -n 's/.*Loaded image: //p' | head -1)
if [ -z "${loaded}" ]; then
echo "::error::No image found in artifact"; exit 1
fi
echo "Loaded ${loaded}"

echo "${DOCKER_TOKEN}" | docker login -u "${DOCKER_USERNAME}" --password-stdin

pushed=""
for t in "${TAG}" "${TAG}_${SHA7}"; do
docker tag "${loaded}" "${DEST}:${t}"
docker push "${DEST}:${t}"
pushed="${pushed} ${DEST}:${t}"
done
echo "tags=$(echo ${pushed} | xargs)" >> "$GITHUB_OUTPUT"

- name: Sticky comment with image tags
if: steps.resolve.outputs.found == 'true' && steps.push.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR: ${{ inputs.pr }}
SHA: ${{ inputs.sha }}
TAGS: ${{ steps.push.outputs.tags }}
run: |
MARKER='<!-- pr-docker-image-sticky -->'
{
echo "${MARKER}"
echo "### 🐳 PR Docker test image"
echo ""
echo "Latest build for commit \`${SHA:0:7}\` pushed to \`dotcms/dotcms-test\`:"
echo ""
echo '```bash'
for t in ${TAGS}; do echo "docker pull ${t}"; done
echo '```'
} > body.md

id=$(gh api "repos/${REPO}/issues/${PR}/comments" -f per_page=100 \
--jq "map(select(.body | contains(\"${MARKER}\"))) | .[0].id // empty")
# gh api has no @file syntax for fields — build the JSON body with jq.
payload=$(jq -nc --rawfile b body.md '{body: $b}')
if [ -n "${id}" ]; then
printf '%s' "${payload}" | gh api -X PATCH "repos/${REPO}/issues/comments/${id}" --input - >/dev/null
echo "Updated sticky comment ${id}"
else
printf '%s' "${payload}" | gh api -X POST "repos/${REPO}/issues/${PR}/comments" --input - >/dev/null
echo "Created sticky comment"
fi
42 changes: 42 additions & 0 deletions .github/workflows/cicd_publish-pr-test-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Publish a per-PR test image when the "PR: docker image" label is applied to an
# existing PR (no new commit). The push-on-commit path lives in cicd_1-pr.yml,
# which publishes right after build. This entry covers the label-applied case by
# reusing the newest docker-image artifact already built for the head SHA.
#
# Uses pull_request_target so it runs from main (DOCKER_* secrets never run in
# PR-modifiable code on this path) and so the GITHUB_TOKEN can comment.
# Internal branches only: fork PRs have no secrets.

name: 'Publish PR Test Image'
run-name: "Publish PR Docker [#${{ github.event.pull_request.number }} ${{ github.event.pull_request.head.ref }}]"

on:
pull_request_target:
types: [labeled]

concurrency:
group: publish-pr-test-image-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read

jobs:
publish:
name: Publish
if: >-
github.event.label.name == 'PR: docker image' &&
github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/workflows/cicd_comp_publish-pr-test-image.yml
with:
pr: ${{ github.event.pull_request.number }}
sha: ${{ github.event.pull_request.head.sha }}
branch: ${{ github.event.pull_request.head.ref }}
# artifact_run_id omitted -> reusable workflow discovers it by SHA
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
permissions:
contents: read
actions: read
pull-requests: write
Loading