diff --git a/Dockerfile b/Dockerfile index 7764248..e167a62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG FROM=python:3.7-alpine +ARG FROM FROM ${FROM} as builder RUN apk add --no-cache \ diff --git a/build-functions/get-public-image-config.sh b/build-functions/get-public-image-config.sh new file mode 100644 index 0000000..d207766 --- /dev/null +++ b/build-functions/get-public-image-config.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Retrieves image configuration from public images in DockerHub +# Functions from https://gist.github.com/cirocosta/17ea17be7ac11594cb0f290b0a3ac0d1 +# Optimised for our use case + +get_image_label() { + local label=$1 + local image=$2 + local tag=$3 + local token=$(_get_token $image) + local digest=$(_get_digest $image $tag $token) + local retval="null" + if [ $digest != "null" ]; then + retval=$(_get_image_configuration $image $token $digest $label) + fi + echo $retval +} + +get_image_layers() { + local image=$1 + local tag=$2 + local token=$(_get_token $image) + _get_layers $image $tag $token +} + +get_image_last_layer() { + local image=$1 + local tag=$2 + local token=$(_get_token $image) + local layers=($(_get_layers $image $tag $token)) + echo ${layers[-1]} +} + +_get_image_configuration() { + local image=$1 + local token=$2 + local digest=$3 + local label=$4 + curl \ + --silent \ + --location \ + --header "Authorization: Bearer $token" \ + "https://registry-1.docker.io/v2/$image/blobs/$digest" \ + | jq -r ".config.Labels.\"$label\"" +} + +_get_token() { + local image=$1 + curl \ + --silent \ + "https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io" \ + | jq -r '.token' +} + +_get_digest() { + local image=$1 + local tag=$2 + local token=$3 + curl \ + --silent \ + --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + --header "Authorization: Bearer $token" \ + "https://registry-1.docker.io/v2/$image/manifests/$tag" \ + | jq -r '.config.digest' +} + +_get_layers() { + local image=$1 + local tag=$2 + local token=$3 + curl \ + --silent \ + --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + --header "Authorization: Bearer $token" \ + "https://registry-1.docker.io/v2/$image/manifests/$tag" \ + | jq -r '.layers[].digest' +} diff --git a/build.sh b/build.sh index 051cffb..5939e63 100755 --- a/build.sh +++ b/build.sh @@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then echo " DOCKERFILE The name of Dockerfile to use." echo " Default: Dockerfile" echo " DOCKER_FROM The base image to use." - echo " Default: Whatever is defined as default in the Dockerfile." + echo " Default: 'python:3.7-alpine'" echo " DOCKER_TARGET A specific target to build." echo " It's currently not possible to pass multiple targets." echo " Default: main ldap" @@ -153,6 +153,11 @@ if [ ! -f "${DOCKERFILE}" ]; then fi fi +### +# Determining the value for DOCKER_FROM +### +DOCKER_FROM="${DOCKER_FROM-python:3.7-alpine}" + ### # Variables for labelling the docker image ### @@ -233,6 +238,49 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do # Proceeding to buils stage, except if `--push-only` is passed ### if [ "${2}" != "--push-only" ] ; then + ### + # Checking if the build is necessary, + # meaning build only if one of those values changed: + # - Python base image digest (Label: PYTHON_BASE_DIGEST) + # - netbox git ref (Label: NETBOX_GIT_REF) + # - netbox-docker git ref (Label: org.label-schema.vcs-ref) + ### + # Load information from registry (only for docker.io) + SHOULD_BUILD="false" + BUILD_REASON="" + if [ -z "${GH_ACTION}" ]; then + # Asuming non Github builds should always proceed + SHOULD_BUILD="true" + BUILD_REASON="${BUILD_REASON} interactive" + fi + if [ $DOCKER_REGISTRY = "docker.io" ] && [ $SHOULD_BUILD = "false" ]; then + source ./build-functions/get-public-image-config.sh + IFS=':' read -ra DOCKER_FROM_SPLIT <<< "${DOCKER_FROM}" + if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ ".*/.*" ]]; then + # Need to use "library/..." for images the have no two part name + DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}" + fi + PYTHON_LAST_LAYER=$(get_image_last_layer ${DOCKER_FROM_SPLIT[0]} ${DOCKER_FROM_SPLIT[1]}) + IMAGES_LAYERS_OLD=($(get_image_layers ${DOCKER_ORG}/${DOCKER_REPO} ${TAG})) + NETBOX_GIT_REF_OLD=$(get_image_label NETBOX_GIT_REF ${DOCKER_ORG}/${DOCKER_REPO} ${TAG}) + GIT_REF_OLD=$(get_image_label org.label-schema.vcs-ref ${DOCKER_ORG}/${DOCKER_REPO} ${TAG}) + + if ! printf '%s\n' ${IMAGES_LAYERS_OLD[@]} | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then + SHOULD_BUILD="true" + BUILD_REASON="${BUILD_REASON} python" + fi + if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then + SHOULD_BUILD="true" + BUILD_REASON="${BUILD_REASON} netbox" + fi + if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then + SHOULD_BUILD="true" + BUILD_REASON="${BUILD_REASON} netbox-docker" + fi + else + SHOULD_BUILD="true" + BUILD_REASON="${BUILD_REASON} no-check" + fi ### # Composing all arguments for `docker build` ### @@ -269,6 +317,10 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do --label "NETBOX_GIT_URL=${NETBOX_GIT_URL}" ) fi + if [ -n "${BUILD_REASON}" ]; then + BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$BUILD_REASON") + DOCKER_BUILD_ARGS+=( --label "BUILD_REASON=${BUILD_REASON}" ) + fi # --build-arg DOCKER_BUILD_ARGS+=( --build-arg "NETBOX_PATH=${NETBOX_PATH}" ) @@ -287,9 +339,15 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do ### # Building the docker image ### - echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'." - $DRY docker build "${DOCKER_BUILD_ARGS[@]}" . - echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'" + if [ "${SHOULD_BUILD}" == "true" ]; then + echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'." + echo " Build reason set to: ${BUILD_REASON}" + $DRY docker build "${DOCKER_BUILD_ARGS[@]}" . + echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'" + else + echo "Build skipped because sources didn't change" + echo "::set-output name=skipped::true" + fi fi ###