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.
This commit is contained in:
Tobias Genannt 2020-04-08 15:45:56 +02:00
parent ed0d099df7
commit 8e34f46bad
3 changed files with 140 additions and 5 deletions

View File

@ -1,4 +1,4 @@
ARG FROM=python:3.7-alpine ARG FROM
FROM ${FROM} as builder FROM ${FROM} as builder
RUN apk add --no-cache \ RUN apk add --no-cache \

View File

@ -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'
}

View File

@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
echo " DOCKERFILE The name of Dockerfile to use." echo " DOCKERFILE The name of Dockerfile to use."
echo " Default: Dockerfile" echo " Default: Dockerfile"
echo " DOCKER_FROM The base image to use." 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 " DOCKER_TARGET A specific target to build."
echo " It's currently not possible to pass multiple targets." echo " It's currently not possible to pass multiple targets."
echo " Default: main ldap" echo " Default: main ldap"
@ -153,6 +153,11 @@ if [ ! -f "${DOCKERFILE}" ]; then
fi fi
fi fi
###
# Determining the value for DOCKER_FROM
###
DOCKER_FROM="${DOCKER_FROM-python:3.7-alpine}"
### ###
# Variables for labelling the docker image # 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 # Proceeding to buils stage, except if `--push-only` is passed
### ###
if [ "${2}" != "--push-only" ] ; then 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` # Composing all arguments for `docker build`
### ###
@ -269,6 +317,10 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
--label "NETBOX_GIT_URL=${NETBOX_GIT_URL}" --label "NETBOX_GIT_URL=${NETBOX_GIT_URL}"
) )
fi 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 # --build-arg
DOCKER_BUILD_ARGS+=( --build-arg "NETBOX_PATH=${NETBOX_PATH}" ) DOCKER_BUILD_ARGS+=( --build-arg "NETBOX_PATH=${NETBOX_PATH}" )
@ -287,9 +339,15 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
### ###
# Building the docker image # Building the docker image
### ###
if [ "${SHOULD_BUILD}" == "true" ]; then
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'." echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
echo " Build reason set to: ${BUILD_REASON}"
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" . $DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'" 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 fi
### ###