Skip to content

Commit 8e34f46

Browse files
committed
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.
1 parent ed0d099 commit 8e34f46

File tree

3 files changed

+140
-5
lines changed

3 files changed

+140
-5
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG FROM=python:3.7-alpine
1+
ARG FROM
22
FROM ${FROM} as builder
33

44
RUN apk add --no-cache \
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/bash
2+
# Retrieves image configuration from public images in DockerHub
3+
# Functions from https://gist.github.com/cirocosta/17ea17be7ac11594cb0f290b0a3ac0d1
4+
# Optimised for our use case
5+
6+
get_image_label() {
7+
local label=$1
8+
local image=$2
9+
local tag=$3
10+
local token=$(_get_token $image)
11+
local digest=$(_get_digest $image $tag $token)
12+
local retval="null"
13+
if [ $digest != "null" ]; then
14+
retval=$(_get_image_configuration $image $token $digest $label)
15+
fi
16+
echo $retval
17+
}
18+
19+
get_image_layers() {
20+
local image=$1
21+
local tag=$2
22+
local token=$(_get_token $image)
23+
_get_layers $image $tag $token
24+
}
25+
26+
get_image_last_layer() {
27+
local image=$1
28+
local tag=$2
29+
local token=$(_get_token $image)
30+
local layers=($(_get_layers $image $tag $token))
31+
echo ${layers[-1]}
32+
}
33+
34+
_get_image_configuration() {
35+
local image=$1
36+
local token=$2
37+
local digest=$3
38+
local label=$4
39+
curl \
40+
--silent \
41+
--location \
42+
--header "Authorization: Bearer $token" \
43+
"https://registry-1.docker.io/v2/$image/blobs/$digest" \
44+
| jq -r ".config.Labels.\"$label\""
45+
}
46+
47+
_get_token() {
48+
local image=$1
49+
curl \
50+
--silent \
51+
"https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io" \
52+
| jq -r '.token'
53+
}
54+
55+
_get_digest() {
56+
local image=$1
57+
local tag=$2
58+
local token=$3
59+
curl \
60+
--silent \
61+
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
62+
--header "Authorization: Bearer $token" \
63+
"https://registry-1.docker.io/v2/$image/manifests/$tag" \
64+
| jq -r '.config.digest'
65+
}
66+
67+
_get_layers() {
68+
local image=$1
69+
local tag=$2
70+
local token=$3
71+
curl \
72+
--silent \
73+
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
74+
--header "Authorization: Bearer $token" \
75+
"https://registry-1.docker.io/v2/$image/manifests/$tag" \
76+
| jq -r '.layers[].digest'
77+
}

build.sh

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
4949
echo " DOCKERFILE The name of Dockerfile to use."
5050
echo " Default: Dockerfile"
5151
echo " DOCKER_FROM The base image to use."
52-
echo " Default: Whatever is defined as default in the Dockerfile."
52+
echo " Default: 'python:3.7-alpine'"
5353
echo " DOCKER_TARGET A specific target to build."
5454
echo " It's currently not possible to pass multiple targets."
5555
echo " Default: main ldap"
@@ -153,6 +153,11 @@ if [ ! -f "${DOCKERFILE}" ]; then
153153
fi
154154
fi
155155

156+
###
157+
# Determining the value for DOCKER_FROM
158+
###
159+
DOCKER_FROM="${DOCKER_FROM-python:3.7-alpine}"
160+
156161
###
157162
# Variables for labelling the docker image
158163
###
@@ -233,6 +238,49 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
233238
# Proceeding to buils stage, except if `--push-only` is passed
234239
###
235240
if [ "${2}" != "--push-only" ] ; then
241+
###
242+
# Checking if the build is necessary,
243+
# meaning build only if one of those values changed:
244+
# - Python base image digest (Label: PYTHON_BASE_DIGEST)
245+
# - netbox git ref (Label: NETBOX_GIT_REF)
246+
# - netbox-docker git ref (Label: org.label-schema.vcs-ref)
247+
###
248+
# Load information from registry (only for docker.io)
249+
SHOULD_BUILD="false"
250+
BUILD_REASON=""
251+
if [ -z "${GH_ACTION}" ]; then
252+
# Asuming non Github builds should always proceed
253+
SHOULD_BUILD="true"
254+
BUILD_REASON="${BUILD_REASON} interactive"
255+
fi
256+
if [ $DOCKER_REGISTRY = "docker.io" ] && [ $SHOULD_BUILD = "false" ]; then
257+
source ./build-functions/get-public-image-config.sh
258+
IFS=':' read -ra DOCKER_FROM_SPLIT <<< "${DOCKER_FROM}"
259+
if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ ".*/.*" ]]; then
260+
# Need to use "library/..." for images the have no two part name
261+
DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}"
262+
fi
263+
PYTHON_LAST_LAYER=$(get_image_last_layer ${DOCKER_FROM_SPLIT[0]} ${DOCKER_FROM_SPLIT[1]})
264+
IMAGES_LAYERS_OLD=($(get_image_layers ${DOCKER_ORG}/${DOCKER_REPO} ${TAG}))
265+
NETBOX_GIT_REF_OLD=$(get_image_label NETBOX_GIT_REF ${DOCKER_ORG}/${DOCKER_REPO} ${TAG})
266+
GIT_REF_OLD=$(get_image_label org.label-schema.vcs-ref ${DOCKER_ORG}/${DOCKER_REPO} ${TAG})
267+
268+
if ! printf '%s\n' ${IMAGES_LAYERS_OLD[@]} | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then
269+
SHOULD_BUILD="true"
270+
BUILD_REASON="${BUILD_REASON} python"
271+
fi
272+
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
273+
SHOULD_BUILD="true"
274+
BUILD_REASON="${BUILD_REASON} netbox"
275+
fi
276+
if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then
277+
SHOULD_BUILD="true"
278+
BUILD_REASON="${BUILD_REASON} netbox-docker"
279+
fi
280+
else
281+
SHOULD_BUILD="true"
282+
BUILD_REASON="${BUILD_REASON} no-check"
283+
fi
236284
###
237285
# Composing all arguments for `docker build`
238286
###
@@ -269,6 +317,10 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
269317
--label "NETBOX_GIT_URL=${NETBOX_GIT_URL}"
270318
)
271319
fi
320+
if [ -n "${BUILD_REASON}" ]; then
321+
BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$BUILD_REASON")
322+
DOCKER_BUILD_ARGS+=( --label "BUILD_REASON=${BUILD_REASON}" )
323+
fi
272324

273325
# --build-arg
274326
DOCKER_BUILD_ARGS+=( --build-arg "NETBOX_PATH=${NETBOX_PATH}" )
@@ -287,9 +339,15 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
287339
###
288340
# Building the docker image
289341
###
290-
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
291-
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
292-
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
342+
if [ "${SHOULD_BUILD}" == "true" ]; then
343+
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
344+
echo " Build reason set to: ${BUILD_REASON}"
345+
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
346+
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
347+
else
348+
echo "Build skipped because sources didn't change"
349+
echo "::set-output name=skipped::true"
350+
fi
293351
fi
294352

295353
###

0 commit comments

Comments
 (0)