feat: MESH Client repository PAT access #837
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docker Image CI | ||
| 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 | ||