name: Reusable Docker Publish on: workflow_call: inputs: image_name: required: true type: string dockerfile_path: type: string default: "." floating_tag: type: string default: "edge" run_image_scan: type: boolean default: true scan_severity: type: string default: "HIGH,CRITICAL" use_private_index: # pass the private Gitea PyPI index into the build as a BuildKit secret, for images # whose Dockerfile installs the in-house BB* packages. The Dockerfile must consume it # via `RUN --mount=type=secret,id=pip_extra_index`, never as an ARG or ENV, so the # token is not baked into an image layer. type: boolean default: false secrets: REGISTRY_USERNAME: { required: true } REGISTRY_PASSWORD: { required: true } DOCKER_REGISTRY: { required: true } NTFY_TOPIC: { required: true } NTFY_TOKEN: { required: true } NTFY_SERVER: { required: true } PIP_EXTRA_INDEX_URL: { required: false } jobs: publish: runs-on: docker container: image: gitea.tech-buddy.at/bitbuddydev/gitea_runner_python314:dev-bda315b82bb23d83065b77d91fedf0e20d9accf1 credentials: username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} steps: - name: Checkout uses: actions/checkout@v4 - name: Extract Tags id: vars shell: bash run: | SHA_SHORT="$(git rev-parse --short HEAD)" FULL_IMAGE="${{ secrets.DOCKER_REGISTRY }}/${{ inputs.image_name }}" TAGS="-t ${FULL_IMAGE}:${SHA_SHORT}" if echo "${{ gitea.ref }}" | grep -q '^refs/tags/v'; then VERSION="${{ gitea.ref_name }}" VERSION="${VERSION#v}" MAJOR="$(echo "$VERSION" | cut -d. -f1)" MINOR="$(echo "$VERSION" | cut -d. -f1,2)" TAGS="${TAGS} -t ${FULL_IMAGE}:latest -t ${FULL_IMAGE}:${VERSION} -t ${FULL_IMAGE}:${MINOR} -t ${FULL_IMAGE}:${MAJOR}" fi echo "docker_tags=${TAGS}" >> "$GITEA_OUTPUT" echo "full_image=${FULL_IMAGE}" >> "$GITEA_OUTPUT" - name: Docker Login run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "${{ secrets.DOCKER_REGISTRY }}" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin - name: Build image shell: bash env: PIP_EXTRA_INDEX_URL: ${{ secrets.PIP_EXTRA_INDEX_URL }} run: | set -euo pipefail SHA_SHORT="$(git rev-parse --short HEAD)" FULL_IMAGE="${{ secrets.DOCKER_REGISTRY }}/${{ inputs.image_name }}" # optionally hand the private package index to the build as a BuildKit secret so the # Dockerfile can install the in-house BB* packages without the token entering a layer SECRET_ARGS="" if [ "${{ inputs.use_private_index }}" = "true" ]; then if [ -z "${PIP_EXTRA_INDEX_URL:-}" ]; then echo "use_private_index=true but the PIP_EXTRA_INDEX_URL secret is empty or not set" exit 1 fi SECRET_ARGS="--secret id=pip_extra_index,env=PIP_EXTRA_INDEX_URL" fi DOCKER_BUILDKIT=1 docker build \ ${SECRET_ARGS} \ -t "${FULL_IMAGE}:${SHA_SHORT}" \ ${{ inputs.dockerfile_path }} # Floating tag points at the most recent build on any branch/tag, so # the nightly image-security scan always has a stable target even # before a release exists. ":latest" stays release-only (below). docker tag "${FULL_IMAGE}:${SHA_SHORT}" "${FULL_IMAGE}:${{ inputs.floating_tag }}" if echo "${{ gitea.ref }}" | grep -q '^refs/tags/v'; then VERSION="${{ gitea.ref_name }}" VERSION="${VERSION#v}" MAJOR="$(echo "$VERSION" | cut -d. -f1)" MINOR="$(echo "$VERSION" | cut -d. -f1,2)" docker tag "${FULL_IMAGE}:${SHA_SHORT}" "${FULL_IMAGE}:latest" docker tag "${FULL_IMAGE}:${SHA_SHORT}" "${FULL_IMAGE}:${VERSION}" docker tag "${FULL_IMAGE}:${SHA_SHORT}" "${FULL_IMAGE}:${MINOR}" docker tag "${FULL_IMAGE}:${SHA_SHORT}" "${FULL_IMAGE}:${MAJOR}" fi - name: Image Vulnerability Scan if: ${{ inputs.run_image_scan }} shell: bash run: | set -euo pipefail # Fallback for runner images without Trivy baked in. Pinned version + # checksum, never "latest": Trivy releases were compromised in the # March 2026 supply-chain incident (malicious v0.69.4). Keep in sync # with the ARGs in the PipelineImage Dockerfiles. TRIVY_VERSION=0.71.0 TRIVY_SHA256=30a3d22b23f88c233f1658f562fb477cae3b3e8b4761109d515b7698daf85814 if ! command -v trivy >/dev/null 2>&1; then echo "=== Trivy not in runner image, installing v${TRIVY_VERSION} ===" curl -sSfLO "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" echo "${TRIVY_SHA256} trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" | sha256sum -c - tar -xzf "trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" -C /usr/local/bin trivy rm "trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" fi trivy --version SHA_SHORT="$(git rev-parse --short HEAD)" FULL_IMAGE="${{ secrets.DOCKER_REGISTRY }}/${{ inputs.image_name }}" echo "=== Scanning ${FULL_IMAGE}:${SHA_SHORT} (fail on fixable ${{ inputs.scan_severity }}) ===" trivy image \ --exit-code 1 \ --severity "${{ inputs.scan_severity }}" \ --ignore-unfixed \ --scanners vuln,secret \ "${FULL_IMAGE}:${SHA_SHORT}" - name: Push image shell: bash run: | set -euxo pipefail echo "=== Git / Ref Info ===" git rev-parse HEAD git rev-parse --short HEAD echo "gitea.ref=${{ gitea.ref }}" echo "gitea.ref_name=${{ gitea.ref_name }}" SHA_SHORT="$(git rev-parse --short HEAD)" FULL_IMAGE="${{ secrets.DOCKER_REGISTRY }}/${{ inputs.image_name }}" echo "=== Image Info ===" echo "FULL_IMAGE=${FULL_IMAGE}" echo "SHA_SHORT=${SHA_SHORT}" echo "Expected image: ${FULL_IMAGE}:${SHA_SHORT}" echo "=== Local Docker Images ===" docker images | grep "${{ inputs.image_name }}" || true echo "=== Inspect Image ===" docker image inspect "${FULL_IMAGE}:${SHA_SHORT}" >/dev/null echo "=== Docker Auth Check ===" docker info echo "=== Push SHA tag ===" docker push "${FULL_IMAGE}:${SHA_SHORT}" echo "=== Push floating tag (${{ inputs.floating_tag }}) ===" docker push "${FULL_IMAGE}:${{ inputs.floating_tag }}" if echo "${{ gitea.ref }}" | grep -q '^refs/tags/v'; then echo "=== Version tag detected ===" VERSION="${{ gitea.ref_name }}" VERSION="${VERSION#v}" MAJOR="$(echo "$VERSION" | cut -d. -f1)" MINOR="$(echo "$VERSION" | cut -d. -f1,2)" echo "VERSION=${VERSION}" echo "MINOR=${MINOR}" echo "MAJOR=${MAJOR}" echo "=== Push latest ===" docker push "${FULL_IMAGE}:latest" echo "=== Push version ===" docker push "${FULL_IMAGE}:${VERSION}" echo "=== Push minor ===" docker push "${FULL_IMAGE}:${MINOR}" echo "=== Push major ===" docker push "${FULL_IMAGE}:${MAJOR}" else echo "=== No version tag detected, only SHA tag pushed ===" fi