Handle empty config hook section #181
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Publish flant/shell-operator image on hub.docker.com. | |
# Build 'latest' tag if started manually on default branch. | |
# Build 'latest' tag when PR is merged into the default branch. | |
# Build 'v*.*.*' and 'latest' tags on push released tag. | |
name: Publish release image | |
on: | |
workflow_dispatch: | |
pull_request: | |
types: [closed] | |
push: | |
tags: | |
- 'v*' | |
env: | |
QEMU_PLATFORMS: arm64,arm | |
BUILDX_PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7" | |
DOCKER_HUB_REPO: flant/shell-operator | |
GHCR_IO_REPO: ghcr.io/flant/shell-operator | |
jobs: | |
check: | |
name: Check | |
runs-on: ubuntu-latest | |
outputs: | |
run_publish: ${{ steps.check.outputs.run_publish }} | |
image_tag: ${{ steps.check.outputs.image_tag }} | |
additional_tag: ${{ steps.check.outputs.additional_tag }} | |
steps: | |
- uses: actions/github-script@v6 | |
id: check | |
with: | |
script: | | |
const SKIP_LATEST_LABEL_NAME = 'skip/image/latest'; | |
const event = context.payload; | |
const eventName = context.eventName; | |
let runPublish = false; | |
let imageTag = ''; | |
let additionalTag = ''; // Also push additional tag when building a released tag. | |
// Check ref name for manual running. | |
if (eventName === 'workflow_dispatch') { | |
if (event.ref !== 'refs/heads/main') { | |
return core.setFailed(`Detect manual execution for '${event.ref}'. Use only default branch. Skip 'publish latest image'.`); | |
} | |
console.log(`Detect manual execution for default branch. Run 'publish latest image'.`); | |
runPublish = true; | |
imageTag = 'latest'; | |
} | |
// Check for 'skip' label on pull request merge. | |
if (eventName == 'pull_request' && event.action == 'closed') { | |
if (!event.pull_request.merged) { | |
return console.log(`PR ${event.number} not merged. Skip 'publish latest image'.`); | |
} | |
const hasSkipLabel = event.pull_request.labels.some(l => l.name === SKIP_LATEST_LABEL_NAME); | |
if (hasSkipLabel) { | |
return console.log(`Detect skip label '${SKIP_LATEST_LABEL_NAME}' on merged PR ${event.number}. Skip 'publish latest image'.`); | |
} | |
console.log(`No skip labels on merged PR ${event.number}. Run 'publish latest image'.`); | |
runPublish = true; | |
imageTag = 'latest'; | |
} | |
// Check if tag is pushed. | |
if (eventName == 'push') { | |
if (!event.ref.startsWith('refs/tags/')) { | |
return console.log(`Detect non-release ref: ${event.ref}. Skip 'publish latest image'.`); | |
} | |
runPublish = true; | |
imageTag = context.ref.replace('refs/tags/', ''); | |
additionalTag = 'latest'; | |
} | |
console.log(`Outputs: run_publish=${runPublish} image_tag=${imageTag} additional_tag=${additionalTag}`); | |
if (!runPublish) { | |
console.log(`DEBUG: eventName=${eventName} action=${event.action} ref=${event.ref} merged=${event.pull_request && event.pull_request.merged} prLabels=${JSON.stringify(event.pull_request && event.pull_request.labels)}`) | |
return console.log(`Skip 'publish latest image'.`); | |
} | |
core.setOutput('run_publish', runPublish.toString()); | |
core.setOutput('image_tag', imageTag); | |
core.setOutput('additional_tag', additionalTag); | |
publish_image: | |
name: Build and publish | |
runs-on: [ubuntu-latest] | |
needs: | |
- check | |
if: needs.check.outputs.run_publish == 'true' | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Prepare environment | |
env: | |
ADDITIONAL_TAG: ${{ needs.check.outputs.additional_tag }} | |
IMAGE_TAG: ${{ needs.check.outputs.image_tag }} | |
run: | | |
: Setup DOCKER_HUB_IMAGE_NAME, DOCKER_HUB_IMAGE_NAME and APP_VERSION for image build | |
APP_VERSION=${IMAGE_TAG} | |
if [[ $APP_VERSION == "latest" ]] ; then | |
APP_VERSION=${GITHUB_REF#refs/heads/}-${GITHUB_SHA::8}-$(date +'%Y.%m.%d_%H:%M:%S') | |
fi | |
DOCKER_HUB_IMAGE_NAME="${DOCKER_HUB_REPO}:${IMAGE_TAG}" | |
GHCR_IO_IMAGE_NAME="${GHCR_IO_REPO}:${IMAGE_TAG}" | |
if [[ -n $ADDITIONAL_TAG ]] ; then | |
ADDITIONAL_DOCKER_HUB_IMAGE_NAME="${DOCKER_HUB_REPO}:${ADDITIONAL_TAG}" | |
ADDITIONAL_GHCR_IO_IMAGE_NAME="${GHCR_IO_REPO}:${ADDITIONAL_TAG}" | |
fi | |
echo "APP_VERSION=${APP_VERSION}" >> ${GITHUB_ENV} | |
echo "DOCKER_HUB_IMAGE_NAME=${DOCKER_HUB_IMAGE_NAME}" >> ${GITHUB_ENV} | |
echo "ADDITIONAL_DOCKER_HUB_IMAGE_NAME=${ADDITIONAL_DOCKER_HUB_IMAGE_NAME}" >> ${GITHUB_ENV} | |
echo "GHCR_IO_IMAGE_NAME=${GHCR_IO_IMAGE_NAME}" >> ${GITHUB_ENV} | |
echo "ADDITIONAL_GHCR_IO_IMAGE_NAME=${ADDITIONAL_GHCR_IO_IMAGE_NAME}" >> ${GITHUB_ENV} | |
echo "=========================================" | |
echo "APP_VERSION = $APP_VERSION" | |
echo "DOCKER_HUB_IMAGE_NAME = $DOCKER_HUB_IMAGE_NAME" | |
echo "GHCR_IO_IMAGE_NAME = $GHCR_IO_IMAGE_NAME" | |
[ -n $ADDITIONAL_GHCR_IO_IMAGE_NAME ] && \ | |
echo "ADDITIONAL_DOCKER_HUB_IMAGE_NAME = $ADDITIONAL_DOCKER_HUB_IMAGE_NAME" && \ | |
echo "ADDITIONAL_GHCR_IO_IMAGE_NAME = $ADDITIONAL_GHCR_IO_IMAGE_NAME" | |
echo "=========================================" | |
- name: Set up QEMU | |
uses: docker/[email protected] | |
with: | |
platforms: "${{ env.QEMU_PLATFORMS }}" | |
- name: Set up Docker Buildx | |
id: buildx | |
uses: docker/setup-buildx-action@v3 | |
with: | |
version: latest | |
- name: Login to Github Container Registry | |
uses: docker/[email protected] | |
with: | |
registry: ghcr.io | |
username: ${{ secrets.GHCR_IO_USER }} | |
password: ${{ secrets.GHCR_IO_PASS }} | |
- name: Build and push multi-arch image | |
run: | | |
echo "Build and push $FINAL_IMAGE_NAME with version '$APP_VERSION'." | |
[ -n "$ADDITIONAL_GHCR_IO_IMAGE_NAME" ] && echo "Also push $ADDITIONAL_GHCR_IO_IMAGE_NAME." | |
echo | |
docker buildx build \ | |
--platform $BUILDX_PLATFORMS \ | |
--build-arg appVersion=$APP_VERSION \ | |
$( [ -n "$ADDITIONAL_GHCR_IO_IMAGE_NAME" ] && echo "--tag $ADDITIONAL_GHCR_IO_IMAGE_NAME" ) \ | |
--tag $GHCR_IO_IMAGE_NAME \ | |
--push \ | |
. | |
- name: Inspect binaries | |
run: | | |
# Image for one arhitecture has digest in config field. | |
# Image with multiple manifests has digest in each manifest. | |
manifests=$(docker buildx imagetools inspect "${GHCR_IO_IMAGE_NAME}" --raw) | |
if grep manifests <<<"${manifests}" 2>&1 >/dev/null ; then | |
jq -r '.manifests[]? | .digest + " " + .platform.os + "/" + .platform.architecture' <<<"${manifests}" | |
else | |
echo $(echo -n "${manifests}" | openssl dgst -sha256 | sed s/^.stdin.*\ //) ' linux/amd64' | |
fi \ | |
| while read digest platform ; do | |
if [[ ${BUILDX_PLATFORMS} != *"${platform}"* ]] ; then | |
echo "=====================================" | |
echo "Ignore image for non-runnable platform ${platform}" | |
echo " ${image}" | |
echo "=====================================" | |
continue | |
fi | |
image=${GHCR_IO_IMAGE_NAME}@${digest} | |
echo "=====================================" | |
echo "Inspect image for platform ${platform}" | |
echo " ${image}" | |
echo "=====================================" | |
docker run --rm --platform ${platform} --entrypoint sh ${image} -c \ | |
'apk add file > /dev/null; file /bin/kubectl; file /bin/busybox; file /shell-operator' | |
done | |
- name: Copy image to Docker Hub | |
env: | |
DOCKER_USER: ${{ secrets.DOCKER_USER }} | |
DOCKER_PASS: ${{ secrets.DOCKER_PASS }} | |
run: | | |
echo "Copy ${GHCR_IO_IMAGE_NAME} to ${DOCKER_HUB_IMAGE_NAME} ..." | |
docker run \ | |
-e "DOCKER_USER=${DOCKER_USER}" \ | |
-e "DOCKER_PASS=${DOCKER_PASS}" \ | |
-e "GHCR_IO_IMAGE_NAME=${GHCR_IO_IMAGE_NAME}" \ | |
-e "DOCKER_HUB_IMAGE_NAME=${DOCKER_HUB_IMAGE_NAME}" \ | |
quay.io/skopeo/stable:latest \ | |
copy --all \ | |
--dest-creds ${DOCKER_USER}:${DOCKER_PASS} \ | |
docker://${GHCR_IO_IMAGE_NAME} \ | |
docker://${DOCKER_HUB_IMAGE_NAME} | |
[ -n "$ADDITIONAL_DOCKER_HUB_IMAGE_NAME" ] && \ | |
echo "Copy ${ADDITIONAL_GHCR_IO_IMAGE_NAME} to ${ADDITIONAL_DOCKER_HUB_IMAGE_NAME} ..." && \ | |
docker run \ | |
-e "DOCKER_USER=${DOCKER_USER}" \ | |
-e "DOCKER_PASS=${DOCKER_PASS}" \ | |
-e "GHCR_IO_IMAGE_NAME=${ADDITIONAL_GHCR_IO_IMAGE_NAME}" \ | |
-e "DOCKER_HUB_IMAGE_NAME=${ADDITIONAL_DOCKER_HUB_IMAGE_NAME}" \ | |
quay.io/skopeo/stable:latest \ | |
copy --all \ | |
--dest-creds ${DOCKER_USER}:${DOCKER_PASS} \ | |
docker://${GHCR_IO_IMAGE_NAME} \ | |
docker://${DOCKER_HUB_IMAGE_NAME} |