Skip to content

Commit e78d94f

Browse files
new CI workflow template
1 parent 58f6161 commit e78d94f

File tree

3 files changed

+322
-42
lines changed

3 files changed

+322
-42
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Software Bill of Materials Report
2+
3+
inputs:
4+
build_datetime:
5+
description: Build datetime, set by the CI/CD pipeline workflow
6+
required: true
7+
build_timestamp:
8+
description: Build timestamp, set by the CI/CD pipeline workflow
9+
required: true
10+
project_name:
11+
description: Project Name
12+
required: true
13+
image_name:
14+
description: Docker Image to be scanned
15+
required: true
16+
17+
runs:
18+
using: composite
19+
env:
20+
BUILD_DATETIME: ${{ inputs.build_datetime }}
21+
SBOM_REPOSITORY_REPORT: sbom/sbom-${{ inputs.image_name }}-repository-report
22+
CHECK_DOCKER_IMAGE: ${{ inputs.project_name }}-${{ inputs.image_name }}:latest
23+
FORCE_USE_DOCKER: true
24+
steps:
25+
- name: Create SBOM report
26+
run: |
27+
mkdir sbom
28+
echo "sbom_repository_report=${SBOM_REPOSITORY_REPORT}" >> ${GITHUB_OUTPUT}
29+
${GITHUB_WORKSPACE}/templates/scripts/reports/create-sbom-report.sh
30+
31+
- name: Upload SBOM report as an artefact
32+
uses: actions/upload-artifact@v4
33+
with:
34+
name: sbom-${{ inputs.image_name }}-repository-report.json
35+
path: ${{ env.SBOM_REPOSITORY_REPORT }}.json
36+
retention-days: 21
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Vulnerabilities Report
2+
3+
inputs:
4+
build_datetime:
5+
description: Build datetime, set by the CI/CD pipeline workflow
6+
required: true
7+
build_timestamp:
8+
description: Build timestamp, set by the CI/CD pipeline workflow
9+
required: true
10+
project_name:
11+
description: Project Name
12+
required: true
13+
image_name:
14+
description: Docker Image to be scanned
15+
required: true
16+
sbom_repository_report:
17+
description: File path of SBOM report
18+
required: true
19+
20+
runs:
21+
using: composite
22+
env:
23+
BUILD_DATETIME: ${{ inputs.build_datetime }}
24+
CHECK_DOCKER_IMAGE: ${{ inputs.project_name }}-${{ inputs.image_name }}:latest
25+
FORCE_USE_DOCKER: true
26+
VULNERABILITIES_REPOSITORY_REPORT: ${{ inputs.image_name }}-vulnerabilities-repository-report
27+
VULNERABILITIES_SUMMARY_LOGFILE: ${{ inputs.image_name }}-vulnerabilities-summary.txt
28+
SBOM_REPOSITORY_REPORT: {{ inputs.sbom_repository_report }}
29+
steps:
30+
- name: Create vulnerabilites report
31+
run: |
32+
mkdir vulnerabilities
33+
bash -x ${GITHUB_WORKSPACE}/templates/scripts/reports/scan-vulnerabilities.sh
34+
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
35+
SCAN_RESULTS=$(grype "${{ inputs.image_name }}:latest" --scope all-layers)
36+
37+
# ANSI color codes
38+
RED="\033[0;31m"
39+
RESET="\033[0m"
40+
41+
# Clear existing log file (or create if it doesn't exist)
42+
> "vulnerabilities/${VULNERABILITIES_SUMMARY_LOGFILE}"
43+
44+
for SEVERITY in CRITICAL HIGH MEDIUM; do
45+
{
46+
echo
47+
echo "${{ inputs.image_name }}: vulnerabilities"
48+
echo -e "=== ${RED}${SEVERITY}${RESET} Vulnerabilities list ==="
49+
# If grep finds nothing, we print a fallback message
50+
echo "${SCAN_RESULTS}" | grep -i "${SEVERITY}" || echo "No ${SEVERITY} vulnerabilities found."
51+
} | tee -a "vunerabilities/${VULNERABILITIES_SUMMARY_LOGFILE}"
52+
done
53+
54+
- name: Upload vulnerabilities report
55+
uses: actions/upload-artifact@v4
56+
with:
57+
name: ${{ env.VULNERABILITIES_REPOSITORY_REPORT }}.json
58+
path: |
59+
vulnerabilities/${{ env.VULNERABILITIES_REPOSITORY_REPORT }}.json
60+
vulnerabilities/${{ env.VULNERABILITIES_SUMMARY_LOGFILE }}.json
61+
retention-days: 21
Lines changed: 225 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,252 @@
1-
name: "Build stage"
1+
name: Docker Image CI
22

33
on:
4+
push:
5+
branches:
6+
- main
7+
48
workflow_call:
59
inputs:
6-
build_datetime:
7-
description: "Build datetime, set by the CI/CD pipeline workflow"
8-
required: true
9-
type: string
10-
build_timestamp:
11-
description: "Build timestamp, set by the CI/CD pipeline workflow"
12-
required: true
13-
type: string
14-
build_epoch:
15-
description: "Build epoch, set by the CI/CD pipeline workflow"
10+
environment_tag:
11+
description: Environment of the deployment
1612
required: true
1713
type: string
18-
nodejs_version:
19-
description: "Node.js version, set by the CI/CD pipeline workflow"
14+
default: development
15+
docker_compose_file_csv_list:
16+
description: The path of the compose.yaml file needed to build docker images
2017
required: true
2118
type: string
22-
python_version:
23-
description: "Python version, set by the CI/CD pipeline workflow"
19+
function_app_source_code_path:
20+
description: The source path of the function app source code for the docker builds
2421
required: true
2522
type: string
26-
terraform_version:
27-
description: "Terraform version, set by the CI/CD pipeline workflow"
23+
project_name:
24+
description: The name of the project
2825
required: true
2926
type: string
30-
version:
31-
description: "Version of the software, set by the CI/CD pipeline workflow"
27+
excluded_containers_csv_list:
28+
description: Excluded containers in a comma separated list
3229
required: true
3330
type: string
3431

32+
env:
33+
ENVIRONMENT_TAG: ${{ inputs.environment_tag }}
34+
COMPOSE_FILE: ${{ inputs.docker_compose_file_csv_list }}
35+
PROJECT_NAME: ${{ inputs.project_name }}
36+
USE_AZURECR: ${{ secrets.ACR_NAME != '' }}
37+
3538
jobs:
36-
artefact-1:
37-
name: "Artefact 1"
39+
get-functions:
3840
runs-on: ubuntu-latest
39-
timeout-minutes: 3
41+
permissions:
42+
contents: read
43+
pull-requests: read
44+
id-token: write
45+
outputs:
46+
FUNC_NAMES: ${{ steps.get-function-names.outputs.FUNC_NAMES }}
4047
steps:
41-
- name: "Checkout code"
48+
- uses: actions/checkout@v4
49+
with:
50+
# to allow git diff between HEAD and the previous commit to main branch
51+
fetch-depth: 2
52+
53+
- name: Checkout dtos-devops-templates repository
4254
uses: actions/checkout@v4
43-
- name: "Build artefact 1"
55+
with:
56+
repository: NHSDigital/dtos-devops-templates
57+
path: templates
58+
ref: main
59+
60+
- name: Determine which Docker container(s) to build
61+
id: get-function-names
62+
env:
63+
COMPOSE_FILES_CSV: ${{ inputs.docker_compose_file_csv_list }}
64+
EXCLUDED_CONTAINERS_CSV: ${{ inputs.excluded_containers_csv_list }}
65+
SOURCE_CODE_PATH: ${{ inputs.function_app_source_code_path }}
66+
run: bash ./templates/scripts/deployments/get-docker-names.sh
67+
68+
build-and-push:
69+
runs-on: ubuntu-latest
70+
permissions:
71+
id-token: write
72+
contents: read
73+
pull-requests: read
74+
packages: write
75+
needs: get-functions
76+
strategy:
77+
matrix:
78+
function: ${{ fromJSON(needs.get-functions.outputs.FUNC_NAMES) }}
79+
if: needs.get-functions.outputs.FUNC_NAMES != '[]'
80+
outputs:
81+
pr_num_tag: ${{ steps.tags.outputs.pr_num_tag }}
82+
short_commit_hash: ${{ steps.tags.short_commit_hash }}
83+
steps:
84+
- uses: actions/checkout@v4
85+
with:
86+
fetch-depth: 1
87+
submodules: true
88+
89+
- name: Checkout dtos-devops-templates repository
90+
uses: actions/checkout@v4
91+
with:
92+
repository: NHSDigital/dtos-devops-templates
93+
path: templates
94+
ref: main
95+
96+
- name: Determine PR number
97+
id: pr_ref
98+
env:
99+
GH_TOKEN: ${{ github.token }}
100+
PR_NUMBER: ${{ github.event.pull_request.number }}
44101
run: |
45-
echo "Building artefact 1 ..."
46-
- name: "Check artefact 1"
102+
if [[ "${GITHUB_REF}" == refs/heads/main ]]; then
103+
pulls_json=$(gh api repos/{owner}/{repo}/commits/${GITHUB_SHA}/pulls)
104+
PR_NUMBER=$(echo "$pulls_json" | jq -r 'sort_by(.updated_at) | reverse | .[0].number')
105+
echo "originating_branch: $(echo "$pulls_json" | jq -r 'sort_by(.updated_at) | reverse | .[0].head.ref')"
106+
fi
107+
echo "pr_num_tag: ${PR_NUMBER}"
108+
echo pr_num_tag=${PR_NUMBER} >> ${GITHUB_OUTPUT}
109+
110+
- name: Construct Image Tag Components
111+
id: tags
112+
env:
113+
IMAGE_NAME: ${{ inputs.project_name }}-${{ matrix.function }}
114+
PR_NUMBER: ${{ github.event.pull_request.number }}
115+
REGISTRY_REPOSITORY_PATH: ${{
116+
env.USE_AZURECR == 'true' &&
117+
format('{0}.azurecr.io/{1}-{2}', secrets.ACR_NAME, inputs.project_name, matrix.function) ||
118+
format('ghcr.io/{0}/{1}-{2}', github.repository_owner, inputs.project_name, matrix.function)
119+
}}
47120
run: |
48-
echo "Checking artefact 1 ..."
49-
- name: "Upload artefact 1"
121+
short_commit_hash=$(git rev-parse --short ${GITHUB_SHA})
122+
echo "short_commit_hash: ${short_commit_hash}"
123+
echo "short_commit_hash=${short_commit_hash}" >> ${GITHUB_OUTPUT}
124+
echo "registry_repository_path: ${REGISTRY_REPOSITORY_PATH}"
125+
echo "registry_repository_path=${REGISTRY_REPOSITORY_PATH}" >> ${GITHUB_OUTPUT}
126+
127+
- name: Build Docker image
128+
working-directory: ${{ steps.get-function-names.outputs.DOCKER_COMPOSE_DIR }}
129+
continue-on-error: false
130+
env:
131+
IMAGE_NAME: ${{ inputs.project_name }}-${{ matrix.function }}
132+
PR_NUM_TAG: ${{ steps.pr_ref.outputs.pr_num_tag }}
133+
COMMIT_HASH_TAG: ${{ steps.tags.short_commit_hash }}
134+
REPO_PATH: ${{ steps.tags.registry_repository_path }}
50135
run: |
51-
echo "Uploading artefact 1 ..."
52-
# TODO: Use either action/cache or action/upload-artifact
53-
artefact-2:
54-
name: "Artefact 2"
136+
docker compose -f ${COMPOSE_FILE//,/ -f } -p ${PROJECT_NAME} build --no-cache --pull ${{ matrix.function }}
137+
docker tag ${IMAGE_NAME}:latest "${REPO_PATH}:${COMMIT_HASH_TAG}"
138+
docker tag ${IMAGE_NAME}:latest "${REPO_PATH}:${PR_NUM_TAG}"
139+
docker tag ${IMAGE_NAME}:latest "${REPO_PATH}:${ENVIRONMENT_TAG}"
140+
141+
- name: Az CLI login
142+
if: github.ref == 'refs/heads/main' && env.USE_AZURECR == 'true'
143+
uses: azure/login@v2
144+
with:
145+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
146+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
147+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
148+
149+
- name: Azure Container Registry login
150+
if: github.ref == 'refs/heads/main' && env.USE_AZURECR == 'true'
151+
run: az acr login --name ${{ secrets.ACR_NAME }}
152+
153+
- name: Push Docker image
154+
if: github.ref == 'refs/heads/main'
155+
working-directory: ${{ steps.get-function-names.outputs.DOCKER_COMPOSE_DIR }}
156+
continue-on-error: false
157+
env:
158+
IMAGE_NAME: ${{ inputs.project_name }}-${{ matrix.function }}
159+
PR_NUM_TAG: ${{ steps.pr_ref.outputs.pr_num_tag }}
160+
COMMIT_HASH_TAG: ${{ steps.tags.short_commit_hash }}
161+
REPO_PATH: ${{ steps.tags.registry_repository_path }}
162+
GH_TOKEN: ${{ github.token }}
163+
run: |
164+
docker push "${REPO_PATH}:${COMMIT_HASH_TAG}"
165+
docker push "${REPO_PATH}:${PR_NUM_TAG}"
166+
docker push "${REPO_PATH}:${ENVIRONMENT_TAG}"
167+
168+
- name: Software Bill of Materials (SBOM)
169+
id: sbom
170+
uses: .github/actions/create-sbom-report
171+
with:
172+
project_name: ${{ inputs.project_name }}
173+
image_name: ${{ inputs.project_name }}-${{ matrix.function }}
174+
175+
- name: Vulnerability scan
176+
uses: .github/actions/scan-vulnerabilities
177+
with:
178+
sbom_repository_report: ${{ steps.sbom.outputs.sbom_repository_report }}
179+
project_name: ${{ inputs.project_name }}
180+
image_name: ${{ inputs.project_name }}-${{ matrix.function }}
181+
182+
tag-all-repositories:
183+
name: Append short commit hash to all images
184+
if: github.ref == 'refs/heads/main'
55185
runs-on: ubuntu-latest
56-
timeout-minutes: 3
186+
needs: build-and-push
187+
env:
188+
SHORT_COMMIT_HASH: ${{ needs.build-and-push.outputs.short_commit_hash }}
189+
permissions:
190+
id-token: write
191+
packages: write
57192
steps:
58-
- name: "Checkout code"
193+
- uses: actions/checkout@v4
194+
with:
195+
# to allow git diff between HEAD and the previous commit to main branch
196+
fetch-depth: 2
197+
198+
- name: Checkout dtos-devops-templates repository
59199
uses: actions/checkout@v4
60-
- name: "Build artefact 2"
61-
run: |
62-
echo "Building artefact 2 ..."
63-
- name: "Check artefact 2"
200+
with:
201+
repository: NHSDigital/dtos-devops-templates
202+
path: templates
203+
ref: main
204+
205+
- name: Az CLI login
206+
if: env.USE_AZURECR == 'true'
207+
uses: azure/login@v2
208+
with:
209+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
210+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
211+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
212+
213+
- name: Azure Container Registry login
214+
if: env.USE_AZURECR == 'true'
215+
run: az acr login --name ${{ secrets.ACR_NAME }}
216+
217+
- name: Tag all azurecr.io Docker images
218+
if: env.USE_AZURECR == 'true'
64219
run: |
65-
echo "Checking artefact 2 ..."
66-
- name: "Upload artefact 2"
220+
az acr login --name ${{ secrets.ACR_NAME }}
221+
repositories=$(az acr repository list --name ${ACR_NAME} --output tsv)
222+
for repository in ${repositories}; do
223+
az acr import --name ${ACR_NAME} --source "${ACR_NAME}.azurecr.io/${repository}:${ENVIRONMENT_TAG}" --image "${repository}:${SHORT_COMMIT_HASH}" --force
224+
done
225+
226+
- name: Tag all ghcr.io Docker images
227+
if: env.USE_AZURECR == 'false'
228+
env:
229+
GH_TOKEN: ${{ github.token }}
230+
REGISTRY: ghcr.io/${{ github.repository_owner }}
67231
run: |
68-
echo "Uploading artefact 2 ..."
69-
# TODO: Use either action/cache or action/upload-artifact
232+
packages=$(gh api /repos/{owner}/{repo}/packages?package_type=container --jq '.[].name')
233+
for package in ${packages}; do
234+
docker buildx imagetools create "${REGISTRY}/${package}:${ENVIRONMENT_TAG}" --tag "${REGISTRY}/${package}:${SHORT_COMMIT_HASH}"
235+
done
236+
237+
aggregate-reports:
238+
runs-on: ubuntu-latest
239+
needs: build-and-push
240+
env:
241+
PR_NUM_TAG: ${{ needs.build-and-push.outputs.pr_num_tag }}
242+
steps:
243+
- name: Download all artifacts
244+
uses: actions/download-artifact@v4
245+
with:
246+
path: reports-${{ env.PR_NUM_TAG }}
247+
248+
- name: Aggregate reports
249+
uses: actions/upload-artifact@v4
250+
with:
251+
name: reports-${{ env.PR_NUM_TAG }}
252+
path: reports-${{ env.PR_NUM_TAG }}

0 commit comments

Comments
 (0)