Skip to content

feat: MESH Client repository PAT access #837

feat: MESH Client repository PAT access

feat: MESH Client repository PAT access #837

name: Docker Image CI

Check failure on line 1 in .github/workflows/stage-3-build-images.yaml

View workflow run for this annotation

GitHub Actions / .github/workflows/stage-3-build-images.yaml

Invalid workflow file

(Line: 94, Col: 11): 'token' is already defined, (Line: 186, Col: 11): 'token' is already defined
on:
workflow_call:
inputs:
environment_tag:
description: Environment of the deployment
required: true
type: string
default: development
docker_compose_file:
description: The path of the compose.yaml file needed to build docker images
required: true
type: string
function_app_source_code_path:
description: The source path of the function app source code for the docker builds
required: true
type: string
project_name:
description: The name of the project
required: true
type: string
excluded_containers_csv_list:
description: Excluded containers in a comma separated list
required: true
type: string
build_all_images:
description: Build all images (true) or only changed ones (false)
required: false
type: boolean
default: false
secrets:
client_id:
description: 'The Azure Client ID.'
required: true
tenant_id:
description: 'The Azure Tenant ID.'
required: true
subscription_id:
description: 'The Azure Subscription ID.'
required: true
acr_name:
description: 'The name of the Azure Container Registry.'
required: true
jobs:
get-functions:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
id-token: write
outputs:
FUNC_NAMES: ${{ steps.get-function-names.outputs.FUNC_NAMES }}
DOCKER_COMPOSE_DIR: ${{ steps.get-function-names.outputs.DOCKER_COMPOSE_DIR }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2
token: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout dtos-devops-templates repository
uses: actions/checkout@v6
with:
repository: NHSDigital/dtos-devops-templates
path: templates
ref: main
- name: Determine which Docker container(s) to build
id: get-function-names
env:
COMPOSE_FILES_CSV: ${{ inputs.docker_compose_file }}
EXCLUDED_CONTAINERS_CSV: ${{ inputs.excluded_containers_csv_list }}
SOURCE_CODE_PATH: ${{ inputs.function_app_source_code_path }}
MANUAL_BUILD_ALL: ${{ inputs.build_all_images || false }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: bash scripts/deployment/get-docker-names.sh
build-base-images:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
id-token: write
packages: write
outputs:
DOTNET_BASE_IMAGE: ${{ steps.set-image-tags.outputs.DOTNET_BASE_IMAGE }}
FUNCTION_BASE_IMAGE: ${{ steps.set-image-tags.outputs.FUNCTION_BASE_IMAGE }}
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.MESH_CLIENT_READ_PAT }}
fetch-depth: 1
submodules: 'true'
- name: log in to GHCR
uses: docker/login-action@v3
continue-on-error: false
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Detect base image changes
id: detect-base-image-changes
continue-on-error: false
run: bash scripts/deployment/check-base-image-changes.sh
- name: Build Base Images
id: build-base-images
working-directory: ./
if: ${{ steps.detect-base-image-changes.outputs.BASE_IMAGE_CHANGE == 'true' || github.ref == 'refs/heads/main' }}
continue-on-error: false
run: |
echo "The branch is: ${GITHUB_REF}"
PR_NUM_TAG=$(echo "${GITHUB_REF}" | sed 's/refs\/pull\/\([0-9]*\)\/merge/\1/')
SHORT_COMMIT_HASH=$(git rev-parse --short ${GITHUB_SHA})
if [ "${GITHUB_REF}" == 'refs/heads/main' ]; then
PR_NUM_TAG="main"
fi
docker build -f Dockerfile.dotnet.base -t cohort-manager-dotnet-base:latest .
docker tag cohort-manager-dotnet-base:latest "ghcr.io/nhsdigital/cohort-manager-dotnet-base:${PR_NUM_TAG}"
docker tag cohort-manager-dotnet-base:latest "ghcr.io/nhsdigital/cohort-manager-dotnet-base:${SHORT_COMMIT_HASH}"
docker build -f Dockerfile.function.base -t cohort-manager-function-base:latest .
docker tag cohort-manager-function-base:latest "ghcr.io/nhsdigital/cohort-manager-function-base:${PR_NUM_TAG}"
docker tag cohort-manager-function-base:latest "ghcr.io/nhsdigital/cohort-manager-function-base:${SHORT_COMMIT_HASH}"
docker push "ghcr.io/nhsdigital/cohort-manager-dotnet-base:${PR_NUM_TAG}"
docker push "ghcr.io/nhsdigital/cohort-manager-dotnet-base:${SHORT_COMMIT_HASH}"
docker push "ghcr.io/nhsdigital/cohort-manager-function-base:${PR_NUM_TAG}"
docker push "ghcr.io/nhsdigital/cohort-manager-function-base:${SHORT_COMMIT_HASH}"
if [ "${GITHUB_REF}" == 'refs/heads/main' ]; then
docker tag cohort-manager-dotnet-base:latest "ghcr.io/nhsdigital/cohort-manager-dotnet-base:latest"
docker tag cohort-manager-function-base:latest "ghcr.io/nhsdigital/cohort-manager-function-base:latest"
docker push "ghcr.io/nhsdigital/cohort-manager-dotnet-base:latest"
docker push "ghcr.io/nhsdigital/cohort-manager-function-base:latest"
fi
- name: Set Image Tags
id: set-image-tags
run: |
PR_NUM_TAG=$(echo "${GITHUB_REF}" | sed 's/refs\/pull\/\([0-9]*\)\/merge/\1/')
IMAGE_TAG="latest"
if [[ ${{steps.detect-base-image-changes.outputs.BASE_IMAGE_CHANGE}} == 'true' ]]; then
IMAGE_TAG="${PR_NUM_TAG}"
fi
if [ "${GITHUB_REF}" == 'refs/heads/main' ]; then
IMAGE_TAG="latest"
fi
echo "Image Tag = ${IMAGE_TAG}"
echo "DOTNET_BASE_IMAGE=ghcr.io/nhsdigital/cohort-manager-dotnet-base:${IMAGE_TAG}" >> "${GITHUB_OUTPUT}"
echo "FUNCTION_BASE_IMAGE=ghcr.io/nhsdigital/cohort-manager-function-base:${IMAGE_TAG}" >> "${GITHUB_OUTPUT}"
build-and-push:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
needs: [get-functions, build-base-images]
strategy:
matrix:
function: ${{ fromJSON(needs.get-functions.outputs.FUNC_NAMES) }}
if: needs.get-functions.outputs.FUNC_NAMES != '[]'
outputs:
pr_num_tag: ${{ env.PR_NUM_TAG }}
short_commit_hash: ${{ env.COMMIT_HASH_TAG }}
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.MESH_CLIENT_READ_PAT }}
fetch-depth: 1
submodules: 'true'
- name: Checkout dtos-devops-templates repository
uses: actions/checkout@v6
with:
repository: NHSDigital/dtos-devops-templates
path: templates
ref: main
- name: Az CLI login
if: github.ref == 'refs/heads/main'
uses: azure/login@v2
with:
client-id: ${{ secrets.client_id }}
tenant-id: ${{ secrets.tenant_id }}
subscription-id: ${{ secrets.subscription_id }}
- name: Azure Container Registry login
if: github.ref == 'refs/heads/main'
env:
ACR_NAME: ${{ secrets.acr_name }}
run: az acr login --name ${ACR_NAME}
- name: Create Tags
env:
GH_TOKEN: ${{ github.token }}
ENVIRONMENT_TAG: ${{ inputs.environment_tag }}
continue-on-error: false
run: |
echo "The branch is: ${GITHUB_REF}"
if [[ "${GITHUB_REF}" == refs/pull/*/merge ]]; then
PR_NUM_TAG=$(echo "${GITHUB_REF}" | sed 's/refs\/pull\/\([0-9]*\)\/merge/\1/')
else
PULLS_JSON=$(gh api /repos/{owner}/{repo}/commits/${GITHUB_SHA}/pulls)
ORIGINATING_BRANCH=$(echo ${PULLS_JSON} | jq -r '.[].head.ref' | python3 -c "import sys, urllib.parse; print(urllib.parse.quote_plus(sys.stdin.read().strip()))")
PR_NUM_TAG=$(echo ${PULLS_JSON} | jq -r '.[].number')
echo "ORIGINATING_BRANCH: ${ORIGINATING_BRANCH}"
fi
SHORT_COMMIT_HASH=$(git rev-parse --short ${GITHUB_SHA})
echo "PR_NUM_TAG: pr${PR_NUM_TAG}"
echo "PR_NUM_TAG=pr${PR_NUM_TAG}" >> ${GITHUB_ENV}
echo "Commit hash tag: ${SHORT_COMMIT_HASH}"
echo "COMMIT_HASH_TAG=${SHORT_COMMIT_HASH}" >> ${GITHUB_ENV}
echo "ENVIRONMENT_TAG=${ENVIRONMENT_TAG}" >> ${GITHUB_ENV}
- name: Build and Push Image
working-directory: ${{ steps.get-function-names.outputs.DOCKER_COMPOSE_DIR }}
continue-on-error: false
env:
COMPOSE_FILE: ${{ inputs.docker_compose_file }}
PROJECT_NAME: ${{ inputs.project_name }}
ACR_NAME: ${{ secrets.acr_name }}
DOTNET_BASE_IMAGE : ${{ needs.build-base-images.outputs.DOTNET_BASE_IMAGE }}
FUNCTION_BASE_IMAGE: ${{ needs.build-base-images.outputs.FUNCTION_BASE_IMAGE }}
run: |
function=${{ matrix.function }}
echo PROJECT_NAME: ${PROJECT_NAME}
if [ -z "${function}" ]; then
echo "Function variable is empty. Skipping Docker build."
exit 0
fi
docker compose -f ${COMPOSE_FILE//,/ -f } \
-p ${PROJECT_NAME} \
--profile "*" build --no-cache --pull ${function}
repo_name="${ACR_NAME}.azurecr.io/${PROJECT_NAME}-${function}"
echo $(repo_name)
# Tag the image
echo "Tag the image:"
docker tag ${PROJECT_NAME}-${function}:latest "$repo_name:${COMMIT_HASH_TAG}"
docker tag ${PROJECT_NAME}-${function}:latest "$repo_name:${PR_NUM_TAG}"
docker tag ${PROJECT_NAME}-${function}:latest "$repo_name:${ENVIRONMENT_TAG}"
# If this variable is set, the create-sbom-report.sh script will scan this docker image instead.
export CHECK_DOCKER_IMAGE=${PROJECT_NAME}-${function}:latest
export FORCE_USE_DOCKER=true
export PR_NUM_TAG=${PR_NUM_TAG}
echo "PR_NUM_TAG=${PR_NUM_TAG}" >> ${GITHUB_ENV}
# Push the image to the repository
if [ "${GITHUB_REF}" == 'refs/heads/main' ]; then
docker push "${repo_name}:${COMMIT_HASH_TAG}"
if [ "${PR_NUM_TAG}" != 'pr' ]; then
docker push "${repo_name}:${PR_NUM_TAG}"
fi
docker push "${repo_name}:${ENVIRONMENT_TAG}"
fi
- name: Run Grype Scan and Save Full Report
uses: anchore/scan-action@56e320f818c551f3f035fde89894504f269ad30b
env:
PROJECT_NAME: ${{ inputs.project_name }}
id: grype
with:
image: "${{ env.PROJECT_NAME }}-${{ matrix.function }}:latest"
output-format: 'table'
output-file: grype-report.txt
fail-build: false
- name: Create Custom Summary Log from Report
working-directory: ${{ steps.get-function-names.outputs.DOCKER_COMPOSE_DIR }}
if: steps.grype.outcome == 'success'
env:
PROJECT_NAME: ${{ inputs.project_name }}
run: |
function=${{ matrix.function }}
SCAN_RESULTS=$(cat grype-report.txt)
# ANSI color codes
RED="\033[0;31m"
RESET="\033[0m"
# Define your log file
VULNERABILITIES_SUMMARY_LOGFILE="${PROJECT_NAME}-${function}-vulnerabilities-summary.txt"
echo "VULNERABILITIES_SUMMARY_LOGFILE=$VULNERABILITIES_SUMMARY_LOGFILE" >> $GITHUB_ENV
# Clear existing log file (or create if it doesn't exist)
> "$VULNERABILITIES_SUMMARY_LOGFILE"
for SEVERITY in CRITICAL HIGH MEDIUM; do
{
echo ""
echo "${PROJECT_NAME}-${function}: vulnerabilities"
echo -e "=== ${RED}${SEVERITY}${RESET} Vulnerabilities list ==="
# If grep finds nothing, we print a fallback message
echo "$SCAN_RESULTS" | grep -i "$SEVERITY" || echo "No $SEVERITY vulnerabilities found."
} | tee -a "$VULNERABILITIES_SUMMARY_LOGFILE"
done
- name: Run the SBOM and scan-vulnerabilities script
working-directory: ${{ steps.get-function-names.outputs.DOCKER_COMPOSE_DIR }}
env:
PROJECT_NAME: ${{ inputs.project_name }}
run: |
function=${{ matrix.function }}
export SBOM_REPOSITORY_REPORT="sbom-${function}-repository-report"
echo "SBOM_REPOSITORY_REPORT=$SBOM_REPOSITORY_REPORT" >> $GITHUB_ENV
bash -x ${GITHUB_WORKSPACE}/templates/scripts/reports/create-sbom-report.sh
export VULNERABILITIES_REPOSITORY_REPORT="vulnerabilities-${function}-repository-report"
echo "VULNERABILITIES_REPOSITORY_REPORT=$VULNERABILITIES_REPOSITORY_REPORT" >> $GITHUB_ENV
echo "Running the scan-vulnerabilities script in a look with 10 minutes timeout, with 3 retries..."
retries=3
delay=300 # 5 minutes
count=0
until [ $count -ge $retries ]
do
timeout 10m bash -x ${GITHUB_WORKSPACE}/templates/scripts/reports/scan-vulnerabilities.sh && break
count=$((count+1))
echo "Attempt $count/$retries failed, retrying after $delay seconds..."
sleep $delay
done
if [ $count -eq $retries ]; then
echo "All attempts failed. Exiting with error."
exit 1
fi
- name: Cleanup the docker images
env:
PROJECT_NAME: ${{ inputs.project_name }}
ACR_NAME: ${{ secrets.acr_name }}
run: |
function=${{ matrix.function }}
repo_name="${ACR_NAME}.azurecr.io/${PROJECT_NAME}-${function}"
# Remove the images
docker rmi "${repo_name}:${COMMIT_HASH_TAG}"
docker rmi "${repo_name}:${PR_NUM_TAG}"
docker rmi "${repo_name}:${ENVIRONMENT_TAG}"
docker rmi ${PROJECT_NAME}-${function}:latest
- name: Compress SBOM report
shell: bash
run: |
echo SBOM_REPOSITORY_REPORT: ${SBOM_REPOSITORY_REPORT}
zip "${SBOM_REPOSITORY_REPORT}.json.zip" "${SBOM_REPOSITORY_REPORT}.json"
- name: Upload SBOM report as an artefact
uses: actions/upload-artifact@v4
with:
name: ${{ env.SBOM_REPOSITORY_REPORT }}.json.zip
path: ./${{ env.SBOM_REPOSITORY_REPORT }}.json.zip
retention-days: 21
- name: Compress vulnerabilities report
shell: bash
run: |
echo VULNERABILITIES_REPOSITORY_REPORT: ${VULNERABILITIES_REPOSITORY_REPORT}
zip ${VULNERABILITIES_REPOSITORY_REPORT}.json.zip ${VULNERABILITIES_REPOSITORY_REPORT}.json
- name: Upload vulnerabilities report as an artefact
uses: actions/upload-artifact@v4
with:
name: ${{ env.VULNERABILITIES_REPOSITORY_REPORT }}.json.zip
path: ./${{ env.VULNERABILITIES_REPOSITORY_REPORT }}.json.zip
retention-days: 21
- name: Upload vulnerabilities summary report as an artefact
uses: actions/upload-artifact@v4
with:
name: ${{ env.VULNERABILITIES_SUMMARY_LOGFILE }}
path: ./${{ env.VULNERABILITIES_SUMMARY_LOGFILE }}
retention-days: 21
aggregate-json:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Download SBOM JSON artifacts
uses: actions/download-artifact@v4
with:
path: ./downloaded-artifacts
- name: Combine sbom report JSON files
run: |
zip sbom-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip downloaded-artifacts/**/sbom*.json.zip
- name: Combine vulnerabilities report JSON files
run: |
zip vulnerabilities-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip downloaded-artifacts/**/vulnerabilities*.json.zip
zip vulnerabilities-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip downloaded-artifacts/**/*vulnerabilities-summary*.txt
- name: Upload sbom zip file
uses: actions/upload-artifact@v4
with:
name: aggregated-sbom-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip
path: sbom-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip
- name: Upload repository zip file
uses: actions/upload-artifact@v4
with:
name: aggregated-vulnerabilities-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip
path: vulnerabilities-repository-report-${{ needs.build-and-push.outputs.PR_NUM_TAG }}.zip