Skip to content

Commit ad03cc3

Browse files
Merge branch 'main' into allow-reverts
2 parents 033000f + 91d5906 commit ad03cc3

26 files changed

Lines changed: 2822 additions & 1009 deletions

.devcontainer/Dockerfile

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,14 @@
1-
FROM mcr.microsoft.com/devcontainers/base:ubuntu
2-
3-
# provide DOCKER_GID via build args if you need to force group id to match host
4-
ARG DOCKER_GID
1+
ARG IMAGE_NAME=node_24_python_3_14
2+
ARG IMAGE_VERSION=latest
3+
FROM ghcr.io/nhsdigital/eps-devcontainers/${IMAGE_NAME}:${IMAGE_VERSION}
54

5+
USER root
66
# specify DOCKER_GID to force container docker group id to match host
77
RUN if [ -n "${DOCKER_GID}" ]; then \
8-
if ! getent group docker; then \
9-
groupadd -g ${DOCKER_GID} docker; \
10-
else \
11-
groupmod -g ${DOCKER_GID} docker; \
12-
fi && \
13-
usermod -aG docker vscode; \
8+
if ! getent group docker; then \
9+
groupadd -g ${DOCKER_GID} docker; \
10+
else \
11+
groupmod -g ${DOCKER_GID} docker; \
12+
fi && \
13+
usermod -aG docker vscode; \
1414
fi
15-
16-
# Anticipate and resolve potential permission issues with apt
17-
RUN mkdir -p /tmp && chmod 1777 /tmp
18-
19-
RUN apt-get update \
20-
&& export DEBIAN_FRONTEND=noninteractive \
21-
&& apt-get -y dist-upgrade \
22-
&& apt-get -y install --no-install-recommends htop vim curl git build-essential \
23-
libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev libbz2-dev \
24-
zlib1g-dev unixodbc unixodbc-dev libsecret-1-0 libsecret-1-dev libsqlite3-dev \
25-
jq apt-transport-https ca-certificates gnupg-agent \
26-
software-properties-common bash-completion python3-pip make libbz2-dev \
27-
libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \
28-
xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev
29-
30-
USER vscode
31-
32-
# Install ASDF
33-
RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 && \
34-
echo '. $HOME/.asdf/asdf.sh' >> ~/.bashrc && \
35-
echo '. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc
36-
37-
ENV PATH="$PATH:/home/vscode/.asdf/bin/:/workspaces/eps-prescription-tracker-ui/node_modules/.bin:/workspaces/eps-common-workflows/.venv/bin"
38-
39-
# Install ASDF plugins#
40-
RUN asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git && \
41-
asdf plugin add actionlint && \
42-
asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git && \
43-
asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git && \
44-
asdf plugin add python
45-
46-
WORKDIR /workspaces/eps-common-workflows
47-
48-
ADD .tool-versions /workspaces/eps-common-workflows/.tool-versions
49-
ADD .tool-versions /home/vscode/.tool-versions
50-
51-
RUN asdf install python && \
52-
asdf install && \
53-
asdf reshim nodejs

.devcontainer/devcontainer.json

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,48 @@
1-
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2-
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
31
{
4-
"name": "Ubuntu",
5-
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6-
"build": {
7-
"dockerfile": "Dockerfile",
8-
"context": "..",
9-
"args": {
10-
"DOCKER_GID": "${env:DOCKER_GID:}"
11-
}
12-
},
13-
"mounts": [
14-
"source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind",
15-
"source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind",
16-
"source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind",
17-
"source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind"
18-
],
19-
"containerUser": "vscode",
20-
"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" },
21-
"postAttachCommand": "docker build -f /workspaces/eps-common-workflows/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . && pre-commit install --install-hooks -f",
22-
"features": {
23-
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
24-
"version": "latest",
25-
"moby": "true",
26-
"installDockerBuildx": "true"
27-
}
2+
"name": "eps-common-workflows",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"context": "..",
6+
"args": {
7+
"DOCKER_GID": "${env:DOCKER_GID:}",
8+
"IMAGE_NAME": "node_24_python_3_14",
9+
"IMAGE_VERSION": "v1.0.6",
10+
"USER_UID": "${localEnv:USER_ID:}",
11+
"USER_GID": "${localEnv:GROUP_ID:}"
2812
},
29-
"customizations": {
30-
"vscode": {
31-
"extensions": [
32-
"AmazonWebServices.aws-toolkit-vscode",
33-
"redhat.vscode-yaml",
34-
"eamodio.gitlens",
35-
"github.vscode-pull-request-github",
36-
"streetsidesoftware.code-spell-checker",
37-
"timonwong.shellcheck",
38-
"github.vscode-github-actions"
39-
],
40-
"settings": {
41-
"cSpell.words": ["fhir", "Formik", "pino", "serialisation"]
42-
}
13+
"updateRemoteUserUID": false
14+
},
15+
"postAttachCommand": "git-secrets --register-aws; git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt",
16+
"mounts": [
17+
"source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind",
18+
"source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind",
19+
"source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind",
20+
"source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind"
21+
],
22+
"containerUser": "vscode",
23+
"remoteEnv": {
24+
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
25+
},
26+
"features": {},
27+
"customizations": {
28+
"vscode": {
29+
"extensions": [
30+
"AmazonWebServices.aws-toolkit-vscode",
31+
"redhat.vscode-yaml",
32+
"eamodio.gitlens",
33+
"github.vscode-pull-request-github",
34+
"streetsidesoftware.code-spell-checker",
35+
"timonwong.shellcheck",
36+
"github.vscode-github-actions"
37+
],
38+
"settings": {
39+
"cSpell.words": [
40+
"fhir",
41+
"Formik",
42+
"pino",
43+
"serialisation"
44+
]
4345
}
4446
}
4547
}
48+
}

.gitallowed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ password: \${{ secrets\.GITHUB_TOKEN }}
55
def __init__\(self, token: str, owner: str, repo: str.*
66
self\.token = token
77
token = os\.environ\.get\(\"GH_TOKEN\"\)
8+
poetry\.lock
89
\-Dsonar\.token=\"\$SONAR_TOKEN\"
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
name: Get Repo Config and Image
2+
on:
3+
workflow_call:
4+
inputs:
5+
registry:
6+
required: false
7+
type: string
8+
default: ghcr.io
9+
namespace:
10+
required: false
11+
type: string
12+
default: nhsdigital/eps-devcontainers
13+
owner:
14+
required: false
15+
type: string
16+
default: NHSDigital
17+
verify_published_from_main_image:
18+
required: false
19+
type: boolean
20+
default: true
21+
predicate_type:
22+
required: false
23+
type: string
24+
default: https://slsa.dev/provenance/v1
25+
outputs:
26+
tag_format:
27+
description: The tag format to be used for releases, as defined in .github/config/settings.yml
28+
value: ${{ jobs.get_config_values.outputs.tag_format }}
29+
devcontainer_image:
30+
description: The devcontainer image name as defined in .devcontainer/devcontainer.json
31+
value: ${{ jobs.get_config_values.outputs.devcontainer_image }}
32+
devcontainer_version:
33+
description: The devcontainer image version as defined in .devcontainer/devcontainer.json
34+
value: ${{ jobs.get_config_values.outputs.devcontainer_version }}
35+
pinned_image:
36+
description: Fully-qualified digest-pinned image reference
37+
value: ${{ jobs.verify_attestation.outputs.pinned_image }}
38+
resolved_digest:
39+
description: Resolved digest for the supplied image reference
40+
value: ${{ jobs.verify_attestation.outputs.resolved_digest }}
41+
42+
jobs:
43+
get_config_values:
44+
runs-on: ubuntu-22.04
45+
outputs:
46+
tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }}
47+
devcontainer_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }}
48+
devcontainer_image: ${{ steps.load-config.outputs.DEVCONTAINER_IMAGE }}
49+
runtime_docker_image: ${{ steps.load-config.outputs.RUNTIME_DOCKER_IMAGE }}
50+
steps:
51+
- name: Checkout code
52+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
53+
with:
54+
ref: ${{ env.BRANCH_NAME }}
55+
fetch-depth: 0
56+
57+
- name: Load config value
58+
id: load-config
59+
run: |
60+
TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml)
61+
DEVCONTAINER_IMAGE=$(jq -r '.build.args.IMAGE_NAME' .devcontainer/devcontainer.json)
62+
DEVCONTAINER_VERSION=$(jq -r '.build.args.IMAGE_VERSION' .devcontainer/devcontainer.json)
63+
RUNTIME_DOCKER_IMAGE="${DEVCONTAINER_IMAGE}:githubactions-${DEVCONTAINER_VERSION}"
64+
{
65+
echo "TAG_FORMAT=$TAG_FORMAT"
66+
echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE"
67+
echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION"
68+
echo "RUNTIME_DOCKER_IMAGE=$RUNTIME_DOCKER_IMAGE"
69+
} >> "$GITHUB_OUTPUT"
70+
71+
verify_attestation:
72+
runs-on: ubuntu-22.04
73+
needs: get_config_values
74+
permissions:
75+
contents: read
76+
packages: read
77+
attestations: read
78+
outputs:
79+
pinned_image: ${{ steps.resolve.outputs.pinned_image }}
80+
resolved_digest: ${{ steps.resolve.outputs.resolved_digest }}
81+
steps:
82+
- name: Login to github container registry
83+
if: startsWith(inputs.registry, 'ghcr.io')
84+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
85+
with:
86+
registry: ghcr.io
87+
username: ${{ github.actor }}
88+
password: ${{ secrets.GITHUB_TOKEN }}
89+
90+
- name: Resolve digest
91+
id: resolve
92+
shell: bash
93+
env:
94+
RUNTIME_DOCKER_IMAGE: ${{ needs.get_config_values.outputs.runtime_docker_image }}
95+
REGISTRY: ${{ inputs.registry }}
96+
NAMESPACE: ${{ inputs.namespace }}
97+
run: |
98+
set -euo pipefail
99+
100+
if [[ "$RUNTIME_DOCKER_IMAGE" == *"/"* ]]; then
101+
IMAGE_REF="$RUNTIME_DOCKER_IMAGE"
102+
else
103+
IMAGE_REF="${REGISTRY}/${NAMESPACE}/${RUNTIME_DOCKER_IMAGE}"
104+
fi
105+
106+
if [[ "$IMAGE_REF" == *@sha256:* ]]; then
107+
IMAGE_BASE="${IMAGE_REF%@*}"
108+
RESOLVED_DIGEST="${IMAGE_REF#*@}"
109+
else
110+
RESOLVED_DIGEST="$(docker buildx imagetools inspect "$IMAGE_REF" | awk '/^Digest:/ {print $2; exit}')"
111+
IMAGE_BASE="${IMAGE_REF%:*}"
112+
fi
113+
114+
if [[ -z "$RESOLVED_DIGEST" ]]; then
115+
echo "Could not resolve digest for image: $IMAGE_REF" >&2
116+
exit 1
117+
fi
118+
119+
PINNED_IMAGE="${IMAGE_BASE}@${RESOLVED_DIGEST}"
120+
echo "resolved_digest=${RESOLVED_DIGEST}" >> "$GITHUB_OUTPUT"
121+
echo "pinned_image=${PINNED_IMAGE}" >> "$GITHUB_OUTPUT"
122+
echo "Resolved image reference: ${IMAGE_REF}"
123+
echo "Resolved digest: ${RESOLVED_DIGEST}"
124+
echo "Resolved image reference: ${PINNED_IMAGE}"
125+
126+
- name: Verify attestation
127+
shell: bash
128+
env:
129+
GH_TOKEN: ${{ github.token }}
130+
OWNER: ${{ inputs.owner }}
131+
VERIFY_PUBLISHED_FROM_MAIN_IMAGE: ${{ inputs.verify_published_from_main_image }}
132+
PREDICATE_TYPE: ${{ inputs.predicate_type }}
133+
PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }}
134+
run: |
135+
set -euo pipefail
136+
137+
args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE")
138+
139+
if [[ "$VERIFY_PUBLISHED_FROM_MAIN_IMAGE" == "true" ]]; then
140+
args+=("--source-ref" "refs/heads/main")
141+
fi
142+
143+
144+
GH_FORCE_TTY=120 gh attestation verify "${args[@]}" 2>&1
145+
echo "Verified attestation for ${PINNED_IMAGE}"

.github/workflows/pull_request.yml

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: pr
1+
name: Pull Request
22

33
on:
44
pull_request:
@@ -14,38 +14,33 @@ jobs:
1414
secrets:
1515
AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }}
1616
AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }}
17+
1718
pr_title_format_check:
1819
uses: ./.github/workflows/pr_title_check.yml
19-
get_asdf_version:
20-
runs-on: ubuntu-22.04
21-
outputs:
22-
asdf_version: ${{ steps.asdf-version.outputs.version }}
23-
tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }}
24-
steps:
25-
- name: Checkout code
26-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
2720

28-
- name: Get asdf version
29-
id: asdf-version
30-
run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT"
31-
- name: Load config value
32-
id: load-config
33-
run: |
34-
TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml)
35-
echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT"
21+
get_config_values:
22+
uses: ./.github/workflows/get-repo-config.yml
23+
with:
24+
verify_published_from_main_image: false
25+
3626
quality_checks:
37-
uses: ./.github/workflows/quality-checks.yml
38-
needs: [get_asdf_version]
27+
uses: ./.github/workflows/quality-checks-devcontainer.yml
28+
needs: [get_config_values]
3929
with:
40-
asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }}
30+
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
4131
secrets:
4232
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
33+
4334
tag_release:
44-
needs: [quality_checks, get_asdf_version]
45-
uses: ./.github/workflows/tag-release.yml
35+
needs: get_config_values
36+
uses: ./.github/workflows/tag-release-devcontainer.yml
37+
permissions:
38+
contents: read
39+
packages: read
40+
attestations: read
4641
with:
4742
dry_run: true
48-
asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }}
43+
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
4944
branch_name: ${{ github.event.pull_request.head.ref }}
50-
tag_format: ${{ needs.get_asdf_version.outputs.tag_format }}
45+
tag_format: ${{ needs.get_config_values.outputs.tag_format }}
5146
secrets: inherit

0 commit comments

Comments
 (0)