Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/actions/lint-terraform/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 0 additions & 4 deletions infrastructure/bootstrap/core.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
12 changes: 7 additions & 5 deletions infrastructure/bootstrap/hub.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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}'

Expand Down Expand Up @@ -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'
}
Expand All @@ -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'
}
Expand All @@ -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'
}
Expand All @@ -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'
}
Expand All @@ -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'
}
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/bootstrap/modules/computeGallery.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/bootstrap/modules/dns.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/bootstrap/modules/privateEndpoint-spoke.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/bootstrap/modules/privateEndpoint.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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' = {
Expand Down
10 changes: 5 additions & 5 deletions infrastructure/bootstrap/modules/storage.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand All @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions scripts/bash/container_app_smoke_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions scripts/bash/resource_group_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion scripts/bash/run_container_app_job.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions scripts/docker/dgoss.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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}")
Expand Down Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions scripts/docker/docker.lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 \
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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" | \
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand Down
Loading