forked from nhs-england-tools/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdocker.lib.sh
345 lines (285 loc) Β· 12.5 KB
/
docker.lib.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#!/bin/bash
# shellcheck disable=SC2155
# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead.
set -euo pipefail
# A set of Docker functions written in Bash.
#
# Usage:
# $ source ./docker.lib.sh
#
# Arguments (provided as environment variables):
# DOCKER_IMAGE=ghcr.io/org/repo # Docker image name
# DOCKER_TITLE="My Docker image" # Docker image title
# TOOL_VERSIONS=$project_dir/.tool-versions # Path to the tool versions file
# ==============================================================================
# Functions to be used with custom images.
# Build Docker image.
# Arguments (provided as environment variables):
# dir=[path to the Dockerfile to use, default is '.']
function docker-build() {
local dir=${dir:-$PWD}
version-create-effective-file
_create-effective-dockerfile
tag=$(_get-effective-tag)
docker build \
--progress=plain \
--platform linux/amd64 \
--build-arg IMAGE="${DOCKER_IMAGE}" \
--build-arg TITLE="${DOCKER_TITLE}" \
--build-arg DESCRIPTION="${DOCKER_TITLE}" \
--build-arg LICENCE=MIT \
--build-arg GIT_URL="$(git config --get remote.origin.url)" \
--build-arg GIT_BRANCH="$(_get-git-branch-name)" \
--build-arg GIT_COMMIT_HASH="$(git rev-parse --short HEAD)" \
--build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%S%z")" \
--build-arg BUILD_VERSION="$(_get-effective-version)" \
--tag "${tag}" \
--rm \
--file "${dir}/Dockerfile.effective" \
.
# Tag the image with all the stated versions, see the documentation for more details
for version in $(_get-all-effective-versions) latest; do
if [ ! -z "$version" ]; then
docker tag "${tag}" "${DOCKER_IMAGE}:${version}"
fi
done
}
# Create the Dockerfile.effective file to bake in version info
# Arguments (provided as environment variables):
# dir=[path to the Dockerfile to use, default is '.']
function docker-bake-dockerfile() {
local dir=${dir:-$PWD}
version-create-effective-file
_create-effective-dockerfile
}
# Run hadolint over the generated Dockerfile.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile.effective is located, default is '.']
function docker-lint() {
local dir=${dir:-$PWD}
file=${dir}/Dockerfile.effective ./scripts/docker/dockerfile-linter.sh
}
# Check test Docker image.
# Arguments (provided as environment variables):
# args=[arguments to pass to Docker to run the container, default is none/empty]
# cmd=[command to pass to the container for execution, default is none/empty]
# dir=[path to the image directory where the Dockerfile is located, default is '.']
# check=[output string to search for]
function docker-check-test() {
local dir=${dir:-$PWD}
# shellcheck disable=SC2086,SC2154
docker run --rm --platform linux/amd64 \
${args:-} \
"${DOCKER_IMAGE}:$(_get-effective-version)" 2>/dev/null \
${cmd:-} \
| grep -q "${check}" && echo PASS || echo FAIL
}
# Run Docker image.
# Arguments (provided as environment variables):
# args=[arguments to pass to Docker to run the container, default is none/empty]
# cmd=[command to pass to the container for execution, default is none/empty]
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function docker-run() {
local dir=${dir:-$PWD}
local tag=$(dir="$dir" _get-effective-tag)
# shellcheck disable=SC2086
docker run --rm --platform linux/amd64 \
${args:-} \
"${tag}" \
${DOCKER_CMD:-}
}
# Push Docker image.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function docker-push() {
local dir=${dir:-$PWD}
# Push all the image tags based on the stated versions, see the documentation for more details
for version in $(dir="$dir" _get-all-effective-versions) latest; do
docker push "${DOCKER_IMAGE}:${version}"
done
}
# Remove Docker resources.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function docker-clean() {
local dir=${dir:-$PWD}
for version in $(dir="$dir" _get-all-effective-versions) latest; do
docker rmi "${DOCKER_IMAGE}:${version}" > /dev/null 2>&1 ||:
done
rm -f \
.version \
Dockerfile.effective \
Dockerfile.effective.dockerignore
}
# Create effective version from the VERSION file.
# Arguments (provided as environment variables):
# dir=[path to the VERSION file to use, default is '.']
# BUILD_DATETIME=[build date and time in the '%Y-%m-%dT%H:%M:%S%z' format generated by the CI/CD pipeline, default is current date and time]
function version-create-effective-file() {
local dir=${dir:-$PWD}
local version_file="$dir/VERSION"
local build_datetime=${BUILD_DATETIME:-$(date -u +'%Y-%m-%dT%H:%M:%S%z')}
if [ -f "$version_file" ]; then
# shellcheck disable=SC2002
cat "$version_file" | \
sed "s/\(\${yyyy}\|\$yyyy\)/$(date --date="${build_datetime}" -u +"%Y")/g" | \
sed "s/\(\${mm}\|\$mm\)/$(date --date="${build_datetime}" -u +"%m")/g" | \
sed "s/\(\${dd}\|\$dd\)/$(date --date="${build_datetime}" -u +"%d")/g" | \
sed "s/\(\${HH}\|\$HH\)/$(date --date="${build_datetime}" -u +"%H")/g" | \
sed "s/\(\${MM}\|\$MM\)/$(date --date="${build_datetime}" -u +"%M")/g" | \
sed "s/\(\${SS}\|\$SS\)/$(date --date="${build_datetime}" -u +"%S")/g" | \
sed "s/\(\${hash}\|\$hash\)/$(git rev-parse --short HEAD)/g" \
> "$dir/.version"
fi
}
# ==============================================================================
# Functions to be used with external images.
# Retrieve the Docker image version from the '.tool-versions' file and pull the
# image if required. This function is to be used in conjunction with the
# external images and it prevents Docker from downloading an image each time it
# is used, since the digest is not stored locally for compressed images. To
# optimise, the solution is to pull the image using its digest and then tag it,
# checking this tag for existence for any subsequent use.
# Arguments (provided as environment variables):
# name=[full name of the Docker image]
# match_version=[regexp to match the version, for example if the same image is used with multiple tags, default is '.*']
# shellcheck disable=SC2001
function docker-get-image-version-and-pull() {
# E.g. for the given entry "# docker/ghcr.io/org/image 1.2.3@sha256:hash" in
# the '.tool-versions' file, the following variables will be set to:
# name="ghcr.io/org/image"
# version="1.2.3@sha256:hash"
# tag="1.2.3"
# digest="sha256:hash"
# Get the image full version from the '.tool-versions' file,
# match it by name and version regex, if given.
local versions_file="${TOOL_VERSIONS:=$(git rev-parse --show-toplevel)/.tool-versions}"
local version="latest"
if [ -f "$versions_file" ]; then
line=$(grep "docker/${name} " "$versions_file" | sed "s/^#\s*//; s/\s*#.*$//" | grep "${match_version:-".*"}")
[ -n "$line" ] && version=$(echo "$line" | awk '{print $2}')
fi
# Split the image version into two, tag name and digest sha256.
local tag="$(echo "$version" | sed 's/@.*$//')"
local digest="$(echo "$version" | sed 's/^.*@//')"
# Check if the image exists locally already
if ! docker images | awk '{ print $1 ":" $2 }' | grep -q "^${name}:${tag}$"; then
if [ "$digest" != "latest" ]; then
# Pull image by the digest sha256 and tag it
docker pull \
--platform linux/amd64 \
"${name}@${digest}" \
> /dev/null 2>&1 || true
docker tag "${name}@${digest}" "${name}:${tag}"
else
# Pull the latest image
docker pull \
--platform linux/amd64 \
"${name}:latest" \
> /dev/null 2>&1 || true
fi
fi
echo "${name}:${version}"
}
# ==============================================================================
# "Private" functions.
# Create effective Dockerfile.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function _create-effective-dockerfile() {
local dir=${dir:-$PWD}
# If it exists, we need to copy the .dockerignore file to match the prefix of the
# Dockerfile.effective file, otherwise docker won't use it.
# See https://docs.docker.com/build/building/context/#filename-and-location
# If using podman, this requires v5.0.0 or later.
if [ -f "${dir}/Dockerfile.dockerignore" ]; then
cp "${dir}/Dockerfile.dockerignore" "${dir}/Dockerfile.effective.dockerignore"
fi
cp "${dir}/Dockerfile" "${dir}/Dockerfile.effective"
_replace-image-latest-by-specific-version
_append-metadata
}
# Replace image:latest by a specific version.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function _replace-image-latest-by-specific-version() {
local dir=${dir:-$PWD}
local versions_file="${TOOL_VERSIONS:=$(git rev-parse --show-toplevel)/.tool-versions}"
local dockerfile="${dir}/Dockerfile.effective"
local build_datetime=${BUILD_DATETIME:-$(date -u +'%Y-%m-%dT%H:%M:%S%z')}
if [ -f "$versions_file" ]; then
# First, list the entries specific for Docker to take precedence, then the rest but exclude comments
content=$(grep " docker/" "$versions_file"; grep -v " docker/" "$versions_file" ||: | grep -v "^#")
echo "$content" | while IFS= read -r line; do
[ -z "$line" ] && continue
line=$(echo "$line" | sed "s/^#\s*//; s/\s*#.*$//" | sed "s;docker/;;")
name=$(echo "$line" | awk '{print $1}')
version=$(echo "$line" | awk '{print $2}')
sed -i "s;\(FROM .*\)${name}:latest;\1${name}:${version};g" "$dockerfile"
done
fi
if [ -f "$dockerfile" ]; then
# shellcheck disable=SC2002
cat "$dockerfile" | \
sed "s/\(\${yyyy}\|\$yyyy\)/$(date --date="${build_datetime}" -u +"%Y")/g" | \
sed "s/\(\${mm}\|\$mm\)/$(date --date="${build_datetime}" -u +"%m")/g" | \
sed "s/\(\${dd}\|\$dd\)/$(date --date="${build_datetime}" -u +"%d")/g" | \
sed "s/\(\${HH}\|\$HH\)/$(date --date="${build_datetime}" -u +"%H")/g" | \
sed "s/\(\${MM}\|\$MM\)/$(date --date="${build_datetime}" -u +"%M")/g" | \
sed "s/\(\${SS}\|\$SS\)/$(date --date="${build_datetime}" -u +"%S")/g" | \
sed "s/\(\${hash}\|\$hash\)/$(git rev-parse --short HEAD)/g" \
> "$dockerfile.tmp"
mv "$dockerfile.tmp" "$dockerfile"
fi
# Do not ignore the issue if 'latest' is used in the effective image
sed -Ei "/# hadolint ignore=DL3007$/d" "${dir}/Dockerfile.effective"
}
# Append metadata to the end of Dockerfile.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function _append-metadata() {
local dir=${dir:-$PWD}
cat \
"$dir/Dockerfile.effective" \
"$(git rev-parse --show-toplevel)/scripts/docker/Dockerfile.metadata" \
> "$dir/Dockerfile.effective.tmp"
mv "$dir/Dockerfile.effective.tmp" "$dir/Dockerfile.effective"
}
# Print top Docker image version.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function _get-effective-version() {
local dir=${dir:-$PWD}
head -n 1 "${dir}/.version" 2> /dev/null ||:
}
# Print the effective tag for the image with the version. If you don't have a VERSION file
# then the tag will be just the image name. Otherwise it will be the image name with the version.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function _get-effective-tag() {
local tag=$DOCKER_IMAGE
version=$(_get-effective-version)
if [ ! -z "$version" ]; then
tag="${tag}:${version}"
fi
echo "$tag"
}
# Print all Docker image versions.
# Arguments (provided as environment variables):
# dir=[path to the image directory where the Dockerfile is located, default is '.']
function _get-all-effective-versions() {
local dir=${dir:-$PWD}
cat "${dir}/.version" 2> /dev/null ||:
}
# Print Git branch name. Check the GitHub variables first and then the local Git
# repo.
function _get-git-branch-name() {
local branch_name=$(git rev-parse --abbrev-ref HEAD)
if [ -n "${GITHUB_HEAD_REF:-}" ]; then
branch_name=$GITHUB_HEAD_REF
elif [ -n "${GITHUB_REF:-}" ]; then
# shellcheck disable=SC2001
branch_name=$(echo "$GITHUB_REF" | sed "s#refs/heads/##")
fi
echo "$branch_name"
}