Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/actions/lint-terraform/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,21 @@ runs:
- name: "Install Terraform binary"
shell: bash
run: |
asdf plugin add terraform || true
asdf install terraform || true
if command -v mise >/dev/null 2>&1 && [[ -f mise.toml ]]; then
MISE_LOCKED=1 mise install terraform
elif command -v asdf >/dev/null 2>&1; then
if ! asdf plugin list | grep -qx terraform; then
asdf plugin add terraform
fi
asdf install terraform
elif ! command -v terraform >/dev/null 2>&1; then
echo "Terraform is unavailable: configure mise or asdf, or provide terraform on PATH." >&2
exit 1
fi
command -v terraform >/dev/null 2>&1 || {
echo "Terraform installation failed; terraform is not on PATH." >&2
exit 1
}
- name: "Check Terraform format"
shell: bash
run: |
Expand Down
5 changes: 5 additions & 0 deletions docs/github-actions/lint-terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Setup mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0

- name: Lint Terraform
uses: NHSDigital/nhs-notify-shared-modules/.github/actions/lint-terraform@v1.0.0
```

### Details

- The caller must set up a version manager before this action. For migrated repositories, set up mise as shown above. Legacy repositories should retain their existing version-manager setup.
- Installs Terraform from `mise.lock` when the checkout has `mise.toml`; otherwise installs it using the legacy version manager and `.tool-versions`. If neither manager is available, Terraform must already be on `PATH`.
- Tool: `terraform fmt`
- Checks: Terraform code formatting
- Validates: HCL syntax and style
Expand Down
73 changes: 60 additions & 13 deletions scripts/docker/docker.lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ set -euo pipefail
# 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
# TOOL_VERSIONS=$project_dir/.docker-versions # Optional override for image versions

# ==============================================================================
# Functions to be used with custom images.
Expand Down Expand Up @@ -144,8 +144,8 @@ function version-create-effective-file() {
# ==============================================================================
# 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
# Retrieve the Docker image version from '.docker-versions', falling back to
# '.tool-versions' during migration, then pull the image if required. Use 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,
Expand All @@ -157,20 +157,34 @@ function version-create-effective-file() {
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:
# the '.docker-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}')
local repository_root
local versions_file
local resolved_version
local -a versions_files
repository_root=$(git rev-parse --show-toplevel)
if [ -n "${TOOL_VERSIONS:-}" ]; then
case "$TOOL_VERSIONS" in
*.docker-versions*) versions_files=("$TOOL_VERSIONS" "${TOOL_VERSIONS/.docker-versions/.tool-versions}") ;;
*.tool-versions*) versions_files=("${TOOL_VERSIONS/.tool-versions/.docker-versions}" "$TOOL_VERSIONS") ;;
*) versions_files=("$TOOL_VERSIONS") ;;
esac
else
versions_files=("$repository_root/.docker-versions" "$repository_root/.tool-versions")
fi
for versions_file in "${versions_files[@]}"; do
resolved_version=$(_docker-image-version-from-file "$versions_file" "$name" "${match_version:-.*}") || true
if [ -n "$resolved_version" ]; then
version="$resolved_version"
break
fi
done

# Split the image version into two, tag name and digest sha256.
local tag="$(echo "$version" | sed 's/@.*$//')"
Expand Down Expand Up @@ -218,13 +232,33 @@ function _create-effective-dockerfile() {
function _replace-image-latest-by-specific-version() {

local dir=${dir:-$PWD}
local versions_file="${TOOL_VERSIONS:=$(git rev-parse --show-toplevel)/.tool-versions}"
local repository_root
local versions_file
local -a versions_files
local dockerfile="${dir}/Dockerfile.effective"
local build_datetime=${BUILD_DATETIME:-$(date -u +'%Y-%m-%dT%H:%M:%S%z')}

if [ -f "$versions_file" ]; then
repository_root=$(git rev-parse --show-toplevel)
if [ -n "${TOOL_VERSIONS:-}" ]; then
case "$TOOL_VERSIONS" in
*.docker-versions*) versions_files=("$TOOL_VERSIONS" "${TOOL_VERSIONS/.docker-versions/.tool-versions}") ;;
*.tool-versions*) versions_files=("${TOOL_VERSIONS/.tool-versions/.docker-versions}" "$TOOL_VERSIONS") ;;
*) versions_files=("$TOOL_VERSIONS") ;;
esac
else
versions_files=("$repository_root/.docker-versions" "$repository_root/.tool-versions")
fi
if [ "${#versions_files[@]}" -gt 0 ]; 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 "^#")
content=""
for versions_file in "${versions_files[@]}"; do
if [ -f "$versions_file" ]; then
content+=$(grep " docker/" "$versions_file" || true)
content+=$'\n'
content+=$(grep -v " docker/" "$versions_file" | grep -v "^#" || true)
content+=$'\n'
fi
done
echo "$content" | while IFS= read -r line; do
[ -z "$line" ] && continue
line=$(echo "$line" | sed "s/^#\s*//; s/\s*#.*$//" | sed "s;docker/;;")
Expand Down Expand Up @@ -252,6 +286,19 @@ function _replace-image-latest-by-specific-version() {
sed -Ei "/# hadolint ignore=DL3007$/d" "${dir}/Dockerfile.effective"
}

function _docker-image-version-from-file() {

local versions_file="$1"
local image_name="$2"
local match_version="$3"
local line

[ -f "$versions_file" ] || return 1
line=$(grep "docker/${image_name} " "$versions_file" | sed "s/^#\\s*//; s/\\s*#.*$//" | grep "$match_version" || true)
[ -n "$line" ] || return 1
echo "$line" | awk '{print $2}'
}

# 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 '.']
Expand Down
2 changes: 2 additions & 0 deletions scripts/docker/tests/.docker-versions.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# python, SEE: https://hub.docker.com/_/python/tags
# docker/python 3.11.4-alpine3.18@sha256:0135ae6442d1269379860b361760ad2cf6ab7c403d21935a8015b48d5bf78a86
40 changes: 38 additions & 2 deletions scripts/docker/tests/docker.test.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# shellcheck disable=SC1091,SC2034,SC2317
# shellcheck disable=SC1091,SC2034,SC2317,SC2329

# 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.

Expand Down Expand Up @@ -28,6 +28,7 @@ function main() {
tests=( \
test-docker-build \
test-docker-image-from-signature \
test-docker-legacy-tool-versions \
test-docker-version-file \
test-docker-test \
test-docker-run \
Expand Down Expand Up @@ -74,14 +75,49 @@ function test-docker-build() {
function test-docker-image-from-signature() {

# Arrange
TOOL_VERSIONS="$(git rev-parse --show-toplevel)/scripts/docker/tests/.tool-versions.test"
TOOL_VERSIONS="$(git rev-parse --show-toplevel)/scripts/docker/tests/.docker-versions.test"
cp Dockerfile Dockerfile.effective
# Act
_replace-image-latest-by-specific-version
# Assert
grep -q "FROM python:.*-alpine.*@sha256:.*" Dockerfile.effective && return 0 || return 1
}

function test-docker-legacy-tool-versions() {

local legacy_root
legacy_root=$(mktemp -d)
git -C "$legacy_root" init -q || return 1
git -C "$legacy_root" -c user.name=test -c user.email=test@example.com commit --allow-empty -qm test || return 1
cp Dockerfile "$legacy_root/Dockerfile"
cp .docker-versions.test "$legacy_root/.tool-versions"
cp Dockerfile "$legacy_root/Dockerfile.effective"

(
cd "$legacy_root" || exit 1
unset TOOL_VERSIONS
_replace-image-latest-by-specific-version
grep -q "FROM python:.*-alpine.*@sha256:.*" Dockerfile.effective || exit 1
cp Dockerfile Dockerfile.effective
TOOL_VERSIONS="$legacy_root/.docker-versions" _replace-image-latest-by-specific-version
grep -q "FROM python:.*-alpine.*@sha256:.*" Dockerfile.effective || exit 1
printf '%s\n' '# docker/python 9.9-alpine@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' > .docker-versions
cp Dockerfile Dockerfile.effective
_replace-image-latest-by-specific-version
grep -q "FROM python:9.9-alpine@sha256:aaaaaaaa" Dockerfile.effective || exit 1
docker() {
case "$1" in
images) return 0 ;;
pull | tag) return 0 ;;
esac
}
name="python" docker-get-image-version-and-pull | grep -q "python:9\\.9-alpine@sha256:"
)
local result=$?
rm -rf "$legacy_root"
return "$result"
}

function test-docker-version-file() {

# Arrange
Expand Down
Loading