From 8e34f46bad21d9467a8e5c24b7b31350e8a0deaf Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Wed, 8 Apr 2020 15:45:56 +0200 Subject: [PATCH] Add checks to verifiy if a new build is needed This checks if the source materials (python image, Netbox commit, netbox-docker commit) have changed since the last build. This check is done by comparing the digest and commit ids from the previous image with the given tag to the current values taken from the Git and Docker repositories. The checks are only performed for builds by the automated builds on Github. --- Dockerfile | 2 +- build-functions/get-public-image-config.sh | 77 ++++++++++++++++++++++ build.sh | 66 +++++++++++++++++-- 3 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 build-functions/get-public-image-config.sh 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 ###