diff --git a/.github/actions/lint-terraform/action.yaml b/.github/actions/lint-terraform/action.yaml index d5dfe35d..8e0de7d0 100644 --- a/.github/actions/lint-terraform/action.yaml +++ b/.github/actions/lint-terraform/action.yaml @@ -13,8 +13,9 @@ runs: check_only=true scripts/githooks/check-terraform-format.sh - name: "Validate Terraform" shell: bash + env: + STACKS: ${{ inputs.root-modules }} run: | - stacks=${{ inputs.root-modules }} - for dir in $(find infrastructure/environments -maxdepth 1 -mindepth 1 -type d; echo ${stacks//,/$'\n'}); do + for dir in $(find infrastructure/environments -maxdepth 1 -mindepth 1 -type d; echo ${STACKS//,/$'\n'}); do dir=$dir make terraform-validate done diff --git a/infrastructure/bootstrap/core.bicep b/infrastructure/bootstrap/core.bicep index 9412f6e8..96a6de47 100644 --- a/infrastructure/bootstrap/core.bicep +++ b/infrastructure/bootstrap/core.bicep @@ -6,10 +6,6 @@ param miPrincipalId string @minLength(1) param miName string -param userGroupPrincipalID string - -param userGroupName string - // See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles var roleID = { contributor: 'b24988ac-6180-42a0-ab88-20f7382dd24c' diff --git a/infrastructure/bootstrap/hub.bicep b/infrastructure/bootstrap/hub.bicep index b17eaa81..25a967fc 100644 --- a/infrastructure/bootstrap/hub.bicep +++ b/infrastructure/bootstrap/hub.bicep @@ -44,6 +44,8 @@ var privateEndpointSubnetName = 'sn-hub-${hubType}-${regionShortName}-private-en var storageAccountName = 'sa${appShortName}${hubType}${regionShortName}state' var computeGalleryName = '${appShortName}_hub_compute_gallery' +var roleDefinitions = 'Microsoft.Authorization/roleDefinitions' + var miADOtoAZname = 'mi-${appShortName}-${hubType}-adotoaz-${regionShortName}' var miGHtoADOname = 'mi-${appShortName}-${hubType}-ghtoado-${regionShortName}' @@ -163,7 +165,7 @@ module managedIdentiyADOtoAZ 'modules/managedIdentity.bicep' = { resource networkContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscription().subscriptionId, hubType, 'networkContributor') properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.networkContributor) + roleDefinitionId: subscriptionResourceId(roleDefinitions, roleID.networkContributor) principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID description: '${miADOtoAZname} Network Contributor access to subscription' } @@ -186,7 +188,7 @@ resource userAccessAdministratorAssignment 'Microsoft.Authorization/roleAssignme resource CDNContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscription().subscriptionId, hubType, 'CDNContributor') properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.CDNContributor) + roleDefinitionId: subscriptionResourceId(roleDefinitions, roleID.CDNContributor) principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID description: '${miADOtoAZname} CDN Contributor access to subscription' } @@ -196,7 +198,7 @@ resource CDNContributorAssignment 'Microsoft.Authorization/roleAssignments@2022- resource TerraformContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscription().subscriptionId, hubType, 'TerraformContributor') properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.contributor) + roleDefinitionId: subscriptionResourceId(roleDefinitions, roleID.contributor) principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID description: '${miADOtoAZname} Terraform Contributor access to subscription' } @@ -206,7 +208,7 @@ resource TerraformContributorAssignment 'Microsoft.Authorization/roleAssignments resource StorageAccountBlobContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscription().subscriptionId, hubType, 'StorageAccountBlobContributorAssignment') properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.storageBlobDataContributor) + roleDefinitionId: subscriptionResourceId(roleDefinitions, roleID.storageBlobDataContributor) principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID description: '${miADOtoAZname} Storage Account Blob Contributor access to subscription' } @@ -230,7 +232,7 @@ module managedIdentiyGHtoADO 'modules/managedIdentity.bicep' = { resource readerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscription().subscriptionId, hubType, 'reader') properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.reader) + roleDefinitionId: subscriptionResourceId(roleDefinitions, roleID.reader) principalId: managedIdentiyGHtoADO.outputs.miPrincipalID description: '${miGHtoADOname} Reader access to subscription' } diff --git a/infrastructure/bootstrap/modules/computeGallery.bicep b/infrastructure/bootstrap/modules/computeGallery.bicep index 3f92e798..3db6e002 100644 --- a/infrastructure/bootstrap/modules/computeGallery.bicep +++ b/infrastructure/bootstrap/modules/computeGallery.bicep @@ -10,7 +10,7 @@ resource computeGallery 'Microsoft.Compute/galleries@2023-07-03' = { name: galleryName location: location properties: { - description: '' + description: 'Compute Gallery for sharing images across subscriptions and regions' softDeletePolicy: { isSoftDeleteEnabled: false } diff --git a/infrastructure/bootstrap/modules/dns.bicep b/infrastructure/bootstrap/modules/dns.bicep index 3a602711..2900fa99 100644 --- a/infrastructure/bootstrap/modules/dns.bicep +++ b/infrastructure/bootstrap/modules/dns.bicep @@ -19,9 +19,9 @@ resource privateDNSZone 'Microsoft.Network/privateDnsZones@2024-06-01' = { } resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = { + location: 'global' name: '${last(split(vnetId, '/'))}-link' parent: privateDNSZone - location: 'global' properties: { virtualNetwork: { id: vnetId diff --git a/infrastructure/bootstrap/modules/privateEndpoint-spoke.bicep b/infrastructure/bootstrap/modules/privateEndpoint-spoke.bicep index 877ce0c8..827187e0 100644 --- a/infrastructure/bootstrap/modules/privateEndpoint-spoke.bicep +++ b/infrastructure/bootstrap/modules/privateEndpoint-spoke.bicep @@ -16,14 +16,14 @@ var groupID = { // Retrieve the existing vnet resource group resource vnetRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = { - name: RGName scope: subscription() + name: RGName } // Retrieve the existing vnet resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' existing = { - name: vnetName scope: vnetRG + name: vnetName } // Retrieve the existing Subnet within the vnet diff --git a/infrastructure/bootstrap/modules/privateEndpoint.bicep b/infrastructure/bootstrap/modules/privateEndpoint.bicep index df58f87b..a24c8a12 100644 --- a/infrastructure/bootstrap/modules/privateEndpoint.bicep +++ b/infrastructure/bootstrap/modules/privateEndpoint.bicep @@ -17,14 +17,14 @@ var groupID = { // Retrieve the existing vnet resource group resource vnetRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = { - name: RGName scope: subscription() + name: RGName } // Retrieve the existing vnet resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' existing = { - name: vnetName scope: vnetRG + name: vnetName } resource privateEndpointSubnet 'Microsoft.Network/virtualNetworks/subnets@2025-01-01' = { diff --git a/infrastructure/bootstrap/modules/storage.bicep b/infrastructure/bootstrap/modules/storage.bicep index 32701636..ea7380a4 100644 --- a/infrastructure/bootstrap/modules/storage.bicep +++ b/infrastructure/bootstrap/modules/storage.bicep @@ -61,11 +61,6 @@ resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/container } } -// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles -var roleID = { - blobContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' -} - // Define role assignments array var roleAssignments = [ { @@ -75,6 +70,11 @@ var roleAssignments = [ } ] +// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles +var roleID = { + blobContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' +} + // Let the managed identity edit the terraform state resource blobContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscription().subscriptionId, miPrincipalID, 'blobContributor') diff --git a/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py b/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py index e574b4ba..a765fb32 100644 --- a/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py +++ b/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py @@ -8,15 +8,16 @@ class TermsOfUseForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + terms_of_use = "Agree to the terms of use to continue" self.fields["value"] = MultipleChoiceField( choices=[(True, 'I agree')], widget=forms.CheckboxSelectMultiple, label="Accept terms of use", label_classes="nhsuk-u-visually-hidden", error_messages={ - "required": "Agree to the terms of use to continue", - "invalid_choice": "Agree to the terms of use to continue", - "invalid_list": "Agree to the terms of use to continue" + "required": terms_of_use, + "invalid_choice": terms_of_use, + "invalid_list": terms_of_use } ) class Meta: diff --git a/scripts/bash/container_app_smoke_test.sh b/scripts/bash/container_app_smoke_test.sh index fc4d15aa..f1479338 100755 --- a/scripts/bash/container_app_smoke_test.sh +++ b/scripts/bash/container_app_smoke_test.sh @@ -8,9 +8,9 @@ DNS_ZONE_NAME=$3 PR_NUMBER=${4:-} USE_APEX_DOMAIN=${5:-false} -if [ -z "$PR_NUMBER" ]; then +if [[ -z "$PR_NUMBER" ]]; then # Permanent environments - if [ "$USE_APEX_DOMAIN" = "true" ]; then + if [[ "$USE_APEX_DOMAIN" = "true" ]]; then # For production with apex domain ENDPOINT="https://${DNS_ZONE_NAME}/sha" else @@ -33,7 +33,7 @@ while true; do # It should fail initially until front door presents the right certificate. if ACTUAL_SHA=$(curl -fsS "$ENDPOINT" 2>/dev/null); then echo "Endpoint responded: $ACTUAL_SHA" - if [ "$ACTUAL_SHA" = "$EXPECTED_SHA" ]; then + if [[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ]]; then echo "✅ SHA matches expected commit: $EXPECTED_SHA" exit 0 else @@ -43,7 +43,7 @@ while true; do now=$(date +%s) elapsed=$((now - start_time)) - if [ $elapsed -ge $TIMEOUT ]; then + if [[ $elapsed -ge $TIMEOUT ]]; then echo "❌ Timeout: Endpoint did not become ready within ${TIMEOUT}s" exit 1 fi diff --git a/scripts/bash/resource_group_init.sh b/scripts/bash/resource_group_init.sh index 1c431ded..7379f43a 100755 --- a/scripts/bash/resource_group_init.sh +++ b/scripts/bash/resource_group_init.sh @@ -15,7 +15,7 @@ userGroupName="screening_${APP_SHORT_NAME}_${ENV_CONFIG}" echo "Fetching object id for group: $userGroupName" userGroupPrincipalID=$(az ad group show --group "$userGroupName" --query id -o tsv) -if [ -z "$userGroupPrincipalID" ]; then +if [[ -z "$userGroupPrincipalID" ]]; then echo "Error: Group '$userGroupName' not found in Entra ID" exit 1 fi @@ -47,5 +47,4 @@ miPrincipalID=$(echo "$output" | jq -r '.properties.outputs.miPrincipalID.value' echo "Deploy to core subscription $ARM_SUBSCRIPTION_ID..." az deployment sub create --location "$REGION" --template-file infrastructure/bootstrap/core.bicep \ --subscription "$ARM_SUBSCRIPTION_ID" \ - --parameters miName="$miName" miPrincipalId="$miPrincipalID" \ - userGroupPrincipalID="$userGroupPrincipalID" userGroupName="$userGroupName" --confirm-with-what-if + --parameters miName="$miName" miPrincipalId="$miPrincipalID" --confirm-with-what-if diff --git a/scripts/bash/run_container_app_job.sh b/scripts/bash/run_container_app_job.sh index bcf1852a..ba72bf10 100755 --- a/scripts/bash/run_container_app_job.sh +++ b/scripts/bash/run_container_app_job.sh @@ -6,7 +6,7 @@ ENV_CONFIG=$1 JOB_SHORT_NAME=$2 PR_NUMBER=${3:-} -if [ -z "$PR_NUMBER" ]; then +if [[ -z "$PR_NUMBER" ]]; then # On permanent environments, the environment name is the environment config name, i.e. "production" ENV=${ENV_CONFIG} else diff --git a/scripts/docker/dgoss.sh b/scripts/docker/dgoss.sh index e573a48b..3db6d745 100644 --- a/scripts/docker/dgoss.sh +++ b/scripts/docker/dgoss.sh @@ -24,7 +24,7 @@ error() { cleanup() { set +e { kill "$log_pid" && wait "$log_pid"; } 2> /dev/null - if [ -n "$CONTAINER_LOG_OUTPUT" ]; then + if [[ -n "$CONTAINER_LOG_OUTPUT" ]]; then cp "$tmp_dir/docker_output.log" "$CONTAINER_LOG_OUTPUT" fi rm -rf "$tmp_dir" @@ -47,7 +47,7 @@ run(){ case "$GOSS_FILES_STRATEGY" in mount) info "Starting $CONTAINER_RUNTIME container" - if [ "$CONTAINER_RUNTIME" == "podman" -a $# == 2 ]; then + if [[ "$CONTAINER_RUNTIME" == "podman" -a $# == 2 ]]; then id=$($CONTAINER_RUNTIME run -d -v "$tmp_dir:/goss:z" "${@:2}" sleep infinity) else id=$($CONTAINER_RUNTIME run -d -v "$tmp_dir:/goss:z" "${@:2}") @@ -113,7 +113,7 @@ case "$1" in fi [[ $GOSS_SLEEP ]] && { info "Sleeping for $GOSS_SLEEP"; sleep "$GOSS_SLEEP"; } info "Container health" - if [ "true" != "$($CONTAINER_RUNTIME inspect -f '{{.State.Running}}' "$id")" ]; then + if [[ "true" != "$($CONTAINER_RUNTIME inspect -f '{{.State.Running}}' "$id")" ]]; then $CONTAINER_RUNTIME logs "$id" >&2 error "the container failed to start" fi diff --git a/scripts/docker/docker.lib.sh b/scripts/docker/docker.lib.sh index d52d651c..fb64cea0 100644 --- a/scripts/docker/docker.lib.sh +++ b/scripts/docker/docker.lib.sh @@ -49,7 +49,7 @@ function docker-build() { # Tag the image with all the stated versions, see the documentation for more details for version in $(_get-all-effective-versions) latest; do - if [ ! -z "$version" ]; then + if [[ ! -z "$version" ]]; then docker tag "${tag}" "${DOCKER_IMAGE}:${version}" fi done @@ -188,7 +188,7 @@ function docker-get-image-version-and-pull() { # 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 + 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}') fi @@ -199,7 +199,7 @@ function docker-get-image-version-and-pull() { # Check if the image exists locally already if ! docker images | awk '{ print $1 ":" $2 }' | grep -q "^${name}:${tag}$"; then - if [ "$digest" != "latest" ]; then + if [[ "$digest" != "latest" ]]; then # Pull image by the digest sha256 and tag it docker pull \ --platform linux/amd64 \ @@ -232,7 +232,7 @@ function _create-effective-dockerfile() { # Dockerfile.effective file, otherwise docker won't use it. # See https://docs.docker.com/build/building/context/#filename-and-location # If using podman, this requires v5.0.0 or later. - if [ -f "${dir}/Dockerfile.dockerignore" ]; then + if [[ -f "${dir}/Dockerfile.dockerignore" ]]; then cp "${dir}/Dockerfile.dockerignore" "${dir}/Dockerfile.effective.dockerignore" fi cp "${dir}/Dockerfile" "${dir}/Dockerfile.effective" @@ -250,7 +250,7 @@ function _replace-image-latest-by-specific-version() { local dockerfile="${dir}/Dockerfile.effective" local build_datetime=${BUILD_DATETIME:-$(date -u +'%Y-%m-%dT%H:%M:%S%z')} - if [ -f "$versions_file" ]; then + if [[ -f "$versions_file" ]]; 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 "^#") echo "$content" | while IFS= read -r line; do @@ -262,7 +262,7 @@ function _replace-image-latest-by-specific-version() { done fi - if [ -f "$dockerfile" ]; then + if [[ -f "$dockerfile" ]]; then # shellcheck disable=SC2002 cat "$dockerfile" | \ sed "s/\(\${yyyy}\|\$yyyy\)/$(date --date="${build_datetime}" -u +"%Y")/g" | \ @@ -312,7 +312,7 @@ function _get-effective-tag() { local tag=$DOCKER_IMAGE version=$(_get-effective-version) - if [ ! -z "$version" ]; then + if [[ ! -z "$version" ]]; then tag="${tag}:${version}" fi echo "$tag" @@ -334,9 +334,9 @@ function _get-git-branch-name() { local branch_name=$(git rev-parse --abbrev-ref HEAD) - if [ -n "${GITHUB_HEAD_REF:-}" ]; then + if [[ -n "${GITHUB_HEAD_REF:-}" ]]; then branch_name=$GITHUB_HEAD_REF - elif [ -n "${GITHUB_REF:-}" ]; then + elif [[ -n "${GITHUB_REF:-}" ]]; then # shellcheck disable=SC2001 branch_name=$(echo "$GITHUB_REF" | sed "s#refs/heads/##") fi