From 904f2620c27cd05ad7ecd5b1e477c1cf7cef4c38 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Mon, 29 Dec 2025 15:53:42 +0000 Subject: [PATCH 01/11] wip --- infrastructure/bootstrap/hub.bicep | 1 + .../bootstrap/modules/managedDevopsPool.bicep | 108 +++++++++--------- 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/infrastructure/bootstrap/hub.bicep b/infrastructure/bootstrap/hub.bicep index 73fb6583..a10a688e 100644 --- a/infrastructure/bootstrap/hub.bicep +++ b/infrastructure/bootstrap/hub.bicep @@ -17,6 +17,7 @@ targetScope = 'subscription' +// param devopsInfrastructureId string param devopsSubnetAddressPrefix string param privateEndpointSubnetAddressPrefix string param hubType string // live / nonlive diff --git a/infrastructure/bootstrap/modules/managedDevopsPool.bicep b/infrastructure/bootstrap/modules/managedDevopsPool.bicep index 416675dc..6bd14b55 100644 --- a/infrastructure/bootstrap/modules/managedDevopsPool.bicep +++ b/infrastructure/bootstrap/modules/managedDevopsPool.bicep @@ -45,57 +45,57 @@ resource devCenterProject 'Microsoft.DevCenter/projects@2025-02-01' = { } } -resource pool 'microsoft.devopsinfrastructure/pools@2025-09-20' = { - name: poolName - location: location - properties: { - organizationProfile: { - organizations: [ - { - url: 'https://dev.azure.com/${adoOrg}' - parallelism: 1 - } - ] - permissionProfile: { - kind: 'CreatorOnly' - } - kind: 'AzureDevOps' - } - devCenterProjectResourceId: devCenterProject.id - maximumConcurrency: poolSize - agentProfile: { - kind: 'Stateful' // or 'Stateless' - VM creation for each job, which tends to be too slow - maxAgentLifetime: agentProfileMaxAgentLifetime // Only allowed if kind is Stateful - // gracePeriodTimeSpan: '00:30:00' // Only allowed if kind is Stateful - resourcePredictionsProfile: { - kind: 'Automatic' // 'Manual' or 'Automatic' - predictionPreference: 'Balanced' - } - } - fabricProfile: { - sku: { - name: fabricProfileSkuName - } - images: [ - { - aliases: [ - 'ubuntu-22.04' - 'ubuntu-22.04/latest' - ] - wellKnownImageName: 'ubuntu-22.04' - } - ] - osProfile: { - logonType: 'Service' // or Interactive - } - storageProfile: { - osDiskStorageAccountType: 'StandardSSD' // StandardSSD, Standard, or Premium - } - // Remove if you want to use 'Isolated Virtual Network' - networkProfile: { - subnetId: devopsSubnet.id - } - kind: 'Vmss' - } - } -} +// resource pool 'microsoft.devopsinfrastructure/pools@2025-09-20' = { +// name: poolName +// location: location +// properties: { +// organizationProfile: { +// organizations: [ +// { +// url: 'https://dev.azure.com/${adoOrg}' +// parallelism: 1 +// } +// ] +// permissionProfile: { +// kind: 'CreatorOnly' +// } +// kind: 'AzureDevOps' +// } +// devCenterProjectResourceId: devCenterProject.id +// maximumConcurrency: poolSize +// agentProfile: { +// kind: 'Stateful' // or 'Stateless' - VM creation for each job, which tends to be too slow +// maxAgentLifetime: agentProfileMaxAgentLifetime // Only allowed if kind is Stateful +// // gracePeriodTimeSpan: '00:30:00' // Only allowed if kind is Stateful +// resourcePredictionsProfile: { +// kind: 'Automatic' // 'Manual' or 'Automatic' +// predictionPreference: 'Balanced' +// } +// } +// fabricProfile: { +// sku: { +// name: fabricProfileSkuName +// } +// images: [ +// { +// aliases: [ +// 'ubuntu-22.04' +// 'ubuntu-22.04/latest' +// ] +// wellKnownImageName: 'ubuntu-22.04' +// } +// ] +// osProfile: { +// logonType: 'Service' // or Interactive +// } +// storageProfile: { +// osDiskStorageAccountType: 'StandardSSD' // StandardSSD, Standard, or Premium +// } +// // Remove if you want to use 'Isolated Virtual Network' +// networkProfile: { +// subnetId: devopsSubnet.id +// } +// kind: 'Vmss' +// } +// } +// } From 3d21f50b5bcfcdd706d4ef0e85a57d2aa1f72e56 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Tue, 30 Dec 2025 10:32:19 +0000 Subject: [PATCH 02/11] wip --- infrastructure/bootstrap/hub.bicep | 4 + .../bootstrap/modules/managedDevopsPool.bicep | 108 +++++++++--------- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/infrastructure/bootstrap/hub.bicep b/infrastructure/bootstrap/hub.bicep index a10a688e..bab30bf2 100644 --- a/infrastructure/bootstrap/hub.bicep +++ b/infrastructure/bootstrap/hub.bicep @@ -25,6 +25,10 @@ param region string = 'uksouth' param regionShortName string = 'uks' param vnetAddressPrefixes array param enableSoftDelete bool +<<<<<<< HEAD +======= + +>>>>>>> 93647fb (wip) // removed when generalised var appShortName = 'lungcs' diff --git a/infrastructure/bootstrap/modules/managedDevopsPool.bicep b/infrastructure/bootstrap/modules/managedDevopsPool.bicep index 6bd14b55..416675dc 100644 --- a/infrastructure/bootstrap/modules/managedDevopsPool.bicep +++ b/infrastructure/bootstrap/modules/managedDevopsPool.bicep @@ -45,57 +45,57 @@ resource devCenterProject 'Microsoft.DevCenter/projects@2025-02-01' = { } } -// resource pool 'microsoft.devopsinfrastructure/pools@2025-09-20' = { -// name: poolName -// location: location -// properties: { -// organizationProfile: { -// organizations: [ -// { -// url: 'https://dev.azure.com/${adoOrg}' -// parallelism: 1 -// } -// ] -// permissionProfile: { -// kind: 'CreatorOnly' -// } -// kind: 'AzureDevOps' -// } -// devCenterProjectResourceId: devCenterProject.id -// maximumConcurrency: poolSize -// agentProfile: { -// kind: 'Stateful' // or 'Stateless' - VM creation for each job, which tends to be too slow -// maxAgentLifetime: agentProfileMaxAgentLifetime // Only allowed if kind is Stateful -// // gracePeriodTimeSpan: '00:30:00' // Only allowed if kind is Stateful -// resourcePredictionsProfile: { -// kind: 'Automatic' // 'Manual' or 'Automatic' -// predictionPreference: 'Balanced' -// } -// } -// fabricProfile: { -// sku: { -// name: fabricProfileSkuName -// } -// images: [ -// { -// aliases: [ -// 'ubuntu-22.04' -// 'ubuntu-22.04/latest' -// ] -// wellKnownImageName: 'ubuntu-22.04' -// } -// ] -// osProfile: { -// logonType: 'Service' // or Interactive -// } -// storageProfile: { -// osDiskStorageAccountType: 'StandardSSD' // StandardSSD, Standard, or Premium -// } -// // Remove if you want to use 'Isolated Virtual Network' -// networkProfile: { -// subnetId: devopsSubnet.id -// } -// kind: 'Vmss' -// } -// } -// } +resource pool 'microsoft.devopsinfrastructure/pools@2025-09-20' = { + name: poolName + location: location + properties: { + organizationProfile: { + organizations: [ + { + url: 'https://dev.azure.com/${adoOrg}' + parallelism: 1 + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + kind: 'AzureDevOps' + } + devCenterProjectResourceId: devCenterProject.id + maximumConcurrency: poolSize + agentProfile: { + kind: 'Stateful' // or 'Stateless' - VM creation for each job, which tends to be too slow + maxAgentLifetime: agentProfileMaxAgentLifetime // Only allowed if kind is Stateful + // gracePeriodTimeSpan: '00:30:00' // Only allowed if kind is Stateful + resourcePredictionsProfile: { + kind: 'Automatic' // 'Manual' or 'Automatic' + predictionPreference: 'Balanced' + } + } + fabricProfile: { + sku: { + name: fabricProfileSkuName + } + images: [ + { + aliases: [ + 'ubuntu-22.04' + 'ubuntu-22.04/latest' + ] + wellKnownImageName: 'ubuntu-22.04' + } + ] + osProfile: { + logonType: 'Service' // or Interactive + } + storageProfile: { + osDiskStorageAccountType: 'StandardSSD' // StandardSSD, Standard, or Premium + } + // Remove if you want to use 'Isolated Virtual Network' + networkProfile: { + subnetId: devopsSubnet.id + } + kind: 'Vmss' + } + } +} From 8942c609ac3d820ce2a430040e9d7022675f3116 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Mon, 9 Feb 2026 15:17:23 +0000 Subject: [PATCH 03/11] PPHA-588: Create the review environment --- infrastructure/bootstrap/hub.bicep | 4 ---- infrastructure/environments/review/variables.sh | 12 ++++++++++++ .../environments/review/variables.tfvars | 17 +++++++++++++++++ .../environments/review/variables.yml | 3 +++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 infrastructure/environments/review/variables.sh create mode 100644 infrastructure/environments/review/variables.tfvars create mode 100644 infrastructure/environments/review/variables.yml diff --git a/infrastructure/bootstrap/hub.bicep b/infrastructure/bootstrap/hub.bicep index bab30bf2..a10a688e 100644 --- a/infrastructure/bootstrap/hub.bicep +++ b/infrastructure/bootstrap/hub.bicep @@ -25,10 +25,6 @@ param region string = 'uksouth' param regionShortName string = 'uks' param vnetAddressPrefixes array param enableSoftDelete bool -<<<<<<< HEAD -======= - ->>>>>>> 93647fb (wip) // removed when generalised var appShortName = 'lungcs' diff --git a/infrastructure/environments/review/variables.sh b/infrastructure/environments/review/variables.sh new file mode 100644 index 00000000..8d235379 --- /dev/null +++ b/infrastructure/environments/review/variables.sh @@ -0,0 +1,12 @@ +REGION=UK South +APP_SHORT_NAME=lungcs +ENV_CONFIG=review +AZURE_SUBSCRIPTION="Lung Cancer Risk Check - Review" +HUB_SUBSCRIPTION="Lung Cancer Risk Check - Non-live hub" +HUB=nonlive +TERRAFORM_MODULES_REF=main +STORAGE_ACCOUNT_RG=rg-hub-nonlive-uks-bootstrap +ENABLE_SOFT_DELETE=false +ADO_MANAGEMENT_POOL=private-pool-hub-nonlive-uks +RUN_NOTIFICATIONS_SMOKE_TEST=true +DOCKER_IMAGE=ghcr.io/nhsdigital/lung_cancer_screening diff --git a/infrastructure/environments/review/variables.tfvars b/infrastructure/environments/review/variables.tfvars new file mode 100644 index 00000000..bd96824d --- /dev/null +++ b/infrastructure/environments/review/variables.tfvars @@ -0,0 +1,17 @@ +dns_zone_name = "review.digital-lung-cancer-screening.nhs.uk" +enable_entra_id_authentication = false +fetch_secrets_from_app_key_vault = true +front_door_profile = "afd-nonlive-hub-lungcs" +features = { + front_door = true + hub_and_spoke = true + private_networking = true +} +# when doing Dev we deploy using +github_mi_name = "mi-lungcs-review-adotoaz-uks" +key_vault_secrets_officer_groups = ["screening_lungcs_review"] +postgres_backup_retention_days = 7 +postgres_geo_redundant_backup_enabled = false +protect_keyvault = false +vnet_address_space = "10.13.0.0/16" +seed_demo_data = true diff --git a/infrastructure/environments/review/variables.yml b/infrastructure/environments/review/variables.yml new file mode 100644 index 00000000..d2160137 --- /dev/null +++ b/infrastructure/environments/review/variables.yml @@ -0,0 +1,3 @@ +OIDC_RP_CLIENT_ID: lcrc +OIDC_OP_FQDN: https://auth.sandpit.signin.nhs.uk +DISABLE_RECENT_SUBMISSION_LIMITATION: true From 19508dd7c271c0e81eb9c122f0df37dbca459d50 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Tue, 10 Feb 2026 10:23:24 +0000 Subject: [PATCH 04/11] wip --- infrastructure/bootstrap/spoke.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/bootstrap/spoke.bicep b/infrastructure/bootstrap/spoke.bicep index e6f97292..f7c98680 100644 --- a/infrastructure/bootstrap/spoke.bicep +++ b/infrastructure/bootstrap/spoke.bicep @@ -12,7 +12,7 @@ var hubMap = { dev: 'nonlive' review: 'nonlive' preprod: 'live' - prd: 'live' + prod: 'live' } var privateEndpointRGName = 'rg-hub-${envConfig}-uks-hub-private-endpoints' var privateDNSZoneRGName = 'rg-hub-${hubMap[envConfig]}-uks-private-dns-zones' From 0e0ef060afc76648d1fc358d2c4411f11091f338 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Wed, 11 Feb 2026 15:12:52 +0000 Subject: [PATCH 05/11] wip --- docs/infrastructure/new-subscription-setup.md | 4 ++++ infrastructure/bootstrap/environments/live/hub.bicepparam | 2 ++ infrastructure/bootstrap/environments/live/variables.sh | 2 +- infrastructure/bootstrap/hub.bicep | 3 ++- infrastructure/bootstrap/modules/managedDevopsPool.bicep | 1 + infrastructure/bootstrap/spoke.bicep | 6 ++++++ infrastructure/environments/review/variables.tfvars | 2 +- 7 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/infrastructure/new-subscription-setup.md b/docs/infrastructure/new-subscription-setup.md index 12b7048c..2a294cb0 100644 --- a/docs/infrastructure/new-subscription-setup.md +++ b/docs/infrastructure/new-subscription-setup.md @@ -66,3 +66,7 @@ Once the feature 'EncryptionAtHost' is registered, invoking 'az provider registe }, "type": "Microsoft.Features/providers/features" }` + + + +DevOpsInfrastructure diff --git a/infrastructure/bootstrap/environments/live/hub.bicepparam b/infrastructure/bootstrap/environments/live/hub.bicepparam index a4b152c5..dbaf999a 100644 --- a/infrastructure/bootstrap/environments/live/hub.bicepparam +++ b/infrastructure/bootstrap/environments/live/hub.bicepparam @@ -5,3 +5,5 @@ param vnetAddressPrefixes = [ '10.21.0.0/16' ] param devopsSubnetAddressPrefix = '10.21.1.0/24' +param privateEndpointSubnetAddressPrefix = '10.21.2.0/24' +param enableSoftDelete = true diff --git a/infrastructure/bootstrap/environments/live/variables.sh b/infrastructure/bootstrap/environments/live/variables.sh index 2fd48789..f4e9cc84 100644 --- a/infrastructure/bootstrap/environments/live/variables.sh +++ b/infrastructure/bootstrap/environments/live/variables.sh @@ -1,3 +1,3 @@ -AZURE_SUBSCRIPTION="name" +AZURE_SUBSCRIPTION="Lung Cancer Risk Check - Live hub" BOOTSTRAP=hub HUB_TYPE=live diff --git a/infrastructure/bootstrap/hub.bicep b/infrastructure/bootstrap/hub.bicep index a10a688e..8d09be54 100644 --- a/infrastructure/bootstrap/hub.bicep +++ b/infrastructure/bootstrap/hub.bicep @@ -9,6 +9,7 @@ Subscription pre-requisites: - az provider register --namespace 'Microsoft.DevOpsInfrastructure' - az provider register --namespace 'Microsoft.DevCenter' + - az provider register --namespace 'Microsoft.Compute' Run once, deployment of the Managed DevOps Pool will fail. Manually Grant 'Reader' and 'Network Contributor' RBAC roles to the Service Principal 'DevopsInfrastructure' on the VNet resource. @@ -41,7 +42,7 @@ var miHub = 'mi-hub-${hubType}-${regionShortName}' var privateDNSZoneRGName = 'rg-hub-${hubType}-${regionShortName}-private-dns-zones' var keyVaultName = 'kv-${appShortName}-${hubType}-inf' var privateEndpointSubnetName = 'sn-hub-${hubType}-${regionShortName}-private-endpoint' -var storageAccountName = 'sa${appShortName}${regionShortName}state' +var storageAccountName = 'sa${appShortName}${hubType}${regionShortName}state' var computeGalleryName = '${appShortName}_hub_compute_gallery' var miADOtoAZname = 'mi-${appShortName}-${hubType}-adotoaz-${regionShortName}' diff --git a/infrastructure/bootstrap/modules/managedDevopsPool.bicep b/infrastructure/bootstrap/modules/managedDevopsPool.bicep index 416675dc..a8677b85 100644 --- a/infrastructure/bootstrap/modules/managedDevopsPool.bicep +++ b/infrastructure/bootstrap/modules/managedDevopsPool.bicep @@ -13,6 +13,7 @@ param devopsSubnetAddressPrefix string param virtualNetworkName string param fabricProfileSkuName string = 'Standard_D2as_v5' +// param fabricProfileSkuName string = 'Standard_D2ads_v5' param poolSize int = 1 param location string = 'uksouth' diff --git a/infrastructure/bootstrap/spoke.bicep b/infrastructure/bootstrap/spoke.bicep index f7c98680..677531b4 100644 --- a/infrastructure/bootstrap/spoke.bicep +++ b/infrastructure/bootstrap/spoke.bicep @@ -1,3 +1,9 @@ + +/* + Subscription pre-requisites: + - az provider register --namespace 'Microsoft.App' +*/ + targetScope='subscription' param enableSoftDelete bool diff --git a/infrastructure/environments/review/variables.tfvars b/infrastructure/environments/review/variables.tfvars index bd96824d..e6daa755 100644 --- a/infrastructure/environments/review/variables.tfvars +++ b/infrastructure/environments/review/variables.tfvars @@ -1,4 +1,4 @@ -dns_zone_name = "review.digital-lung-cancer-screening.nhs.uk" +dns_zone_name = "non-live.digital-lung-cancer-screening.nhs.uk" enable_entra_id_authentication = false fetch_secrets_from_app_key_vault = true front_door_profile = "afd-nonlive-hub-lungcs" From 9056b9b75368f23d64674b3856fa933632d8da97 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Wed, 11 Feb 2026 17:00:13 +0000 Subject: [PATCH 06/11] wip --- infrastructure/modules/infra/main.tf | 2 +- infrastructure/terraform/spoke/data.tf | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/modules/infra/main.tf b/infrastructure/modules/infra/main.tf index e4eb3846..9b9b05a4 100644 --- a/infrastructure/modules/infra/main.tf +++ b/infrastructure/modules/infra/main.tf @@ -62,7 +62,7 @@ module "container-app-environment" { azurerm.dns = azurerm.hub } - name = "cae-${var.environment}-uks-${var.app_short_name}" + name = "cae-${var.envi}-uks-${var.app_short_name}" resource_group_name = azurerm_resource_group.main.name internal_load_balancer_enabled = var.features.private_networking log_analytics_workspace_id = module.log_analytics_workspace_audit.id diff --git a/infrastructure/terraform/spoke/data.tf b/infrastructure/terraform/spoke/data.tf index 49fb73ff..4f0a60ea 100644 --- a/infrastructure/terraform/spoke/data.tf +++ b/infrastructure/terraform/spoke/data.tf @@ -2,7 +2,7 @@ data "azurerm_container_app_environment" "this" { count = var.deploy_infra ? 0 : 1 - name = "cae-${var.environment}-uks-${var.app_short_name}" + name = "cae-${var.env_config}-uks-${var.app_short_name}" resource_group_name = local.resource_group_name } @@ -16,7 +16,7 @@ data "azurerm_key_vault" "app_key_vault" { data "azurerm_log_analytics_workspace" "audit" { count = var.deploy_infra ? 0 : 1 - name = "law-${var.environment}-uks-${var.app_short_name}" + name = "law-${var.env_config}-uks-${var.app_short_name}" resource_group_name = local.resource_group_name } @@ -24,7 +24,7 @@ data "azurerm_subnet" "postgres" { count = var.deploy_infra ? 0 : 1 name = "snet-postgres" - virtual_network_name = "vnet-${var.environment}-uks-${var.app_short_name}" + virtual_network_name = "vnet-${var.env_config}-uks-${var.app_short_name}" resource_group_name = local.resource_group_name } @@ -32,6 +32,6 @@ data "azurerm_subnet" "main" { count = var.deploy_infra ? 0 : 1 name = "snet-main" - virtual_network_name = "vnet-${var.environment}-uks-${var.app_short_name}" + virtual_network_name = "vnet-${var.env_config}-uks-${var.app_short_name}" resource_group_name = local.resource_group_name } From 1822cc018a68b207b16bf28e5290f469543a20c0 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Wed, 11 Feb 2026 17:01:49 +0000 Subject: [PATCH 07/11] wip --- infrastructure/modules/infra/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/infra/main.tf b/infrastructure/modules/infra/main.tf index 9b9b05a4..e4eb3846 100644 --- a/infrastructure/modules/infra/main.tf +++ b/infrastructure/modules/infra/main.tf @@ -62,7 +62,7 @@ module "container-app-environment" { azurerm.dns = azurerm.hub } - name = "cae-${var.envi}-uks-${var.app_short_name}" + name = "cae-${var.environment}-uks-${var.app_short_name}" resource_group_name = azurerm_resource_group.main.name internal_load_balancer_enabled = var.features.private_networking log_analytics_workspace_id = module.log_analytics_workspace_audit.id From 752fd8270d0504a77e9ba00517915f69b76f4454 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Wed, 11 Feb 2026 17:08:13 +0000 Subject: [PATCH 08/11] wip --- infrastructure/environments/review/variables.tfvars | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/environments/review/variables.tfvars b/infrastructure/environments/review/variables.tfvars index e6daa755..05fc9084 100644 --- a/infrastructure/environments/review/variables.tfvars +++ b/infrastructure/environments/review/variables.tfvars @@ -15,3 +15,4 @@ postgres_geo_redundant_backup_enabled = false protect_keyvault = false vnet_address_space = "10.13.0.0/16" seed_demo_data = true +deploy_database_as_container = true From 9c06d8ee8ee51d6b7ab75b6b913058b46789f21b Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Thu, 12 Feb 2026 14:09:48 +0000 Subject: [PATCH 09/11] wip --- docs/infrastructure/create-environment.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/infrastructure/create-environment.md b/docs/infrastructure/create-environment.md index 2f247d36..d909978b 100644 --- a/docs/infrastructure/create-environment.md +++ b/docs/infrastructure/create-environment.md @@ -106,3 +106,7 @@ Add the infrastructure secrets to the _inf_ key vault `kv-lungcs-[environment]-i - Set `fetch_secrets_from_app_key_vault` terraform variable to `true` - Test running terraform manually from the AVD (Optional) - Raise a pull request, review and merge to trigger the pipeline + + +assign yourself "Key Vault Secrets User" to application key vault to run the terraform code from the cli inside the AVD when first trying to deploy the application. +assign yourself "Data Blob Reader" to State file storage account to run the terraform code from the cli inside the AVD when first trying to deploy the application. From 633e540590e6d68a7ad78e958e25f5261abbcf3f Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Thu, 12 Feb 2026 14:31:01 +0000 Subject: [PATCH 10/11] major cd changes --- .../workflows/cicd-1-pull-request-closed.yaml | 49 +++++ .github/workflows/cicd-1-pull-request.yaml | 154 +++++++--------- .github/workflows/stage-4-acceptance.yaml | 172 ------------------ .github/workflows/stage-4-deploy.yaml | 68 +++++++ 4 files changed, 181 insertions(+), 262 deletions(-) create mode 100644 .github/workflows/cicd-1-pull-request-closed.yaml delete mode 100644 .github/workflows/stage-4-acceptance.yaml create mode 100644 .github/workflows/stage-4-deploy.yaml diff --git a/.github/workflows/cicd-1-pull-request-closed.yaml b/.github/workflows/cicd-1-pull-request-closed.yaml new file mode 100644 index 00000000..c017255e --- /dev/null +++ b/.github/workflows/cicd-1-pull-request-closed.yaml @@ -0,0 +1,49 @@ +name: Delete review app + +on: + pull_request: + types: [closed] + +jobs: + destroy: + if: contains(github.event.pull_request.labels.*.name, 'deploy') + name: Delete review app pr-${{ github.event.pull_request.number }} + permissions: + id-token: write + pull-requests: write + runs-on: ubuntu-latest + environment: review + # Prevent concurrent jobs on the same environment and between deploy and delete workflows + concurrency: deploy-review-${{ github.event.pull_request.number }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Call delete review app pipeline + run: | + echo "Starting Azure devops pipeline \"Delete review app\"..." + RUN_ID=$(az pipelines run \ + --commit-id ${{ github.event.pull_request.head.sha }}\ + --name "Delete review app"\ + --org https://dev.azure.com/nhse-dtos \ + --project lung-cancer-risk-check \ + --parameters commitSHA=${{ github.event.pull_request.head.sha }} prNumber=${{ github.event.pull_request.number }} \ + --output tsv --query id) + + echo "See pipeline run in Azure devops: https://dev.azure.com/nhse-dtos/lung-cancer-risk-check/_build/results?buildId=${RUN_ID}&view=results" + + scripts/bash/wait_ado_pipeline.sh "$RUN_ID" https://dev.azure.com/nhse-dtos lung_cancer_screening + + - name: Post URL to PR comments + uses: marocchino/sticky-pull-request-comment@5060d4700a91de252c87eeddd2da026382d9298a + with: + message: | + The review app at this URL has been deleted: + https://pr-${{ github.event.pull_request.number }}.non-live.digital-lung-cancer-screening.nhs.uk diff --git a/.github/workflows/cicd-1-pull-request.yaml b/.github/workflows/cicd-1-pull-request.yaml index a78fc781..79487b28 100644 --- a/.github/workflows/cicd-1-pull-request.yaml +++ b/.github/workflows/cicd-1-pull-request.yaml @@ -1,18 +1,12 @@ -name: "CI/CD pull request" - -# The total recommended execution time for the "CI/CD Pull Request" workflow is around 20 minutes. +name: 'CI/CD pull request' on: - push: - branches: - - "**" - - "!main" pull_request: - types: [opened, reopened] + types: [opened, reopened, synchronize, labeled] jobs: metadata: - name: "Set CI/CD metadata" + name: 'Set CI/CD metadata' runs-on: ubuntu-latest timeout-minutes: 1 outputs: @@ -24,105 +18,85 @@ jobs: python_version: ${{ steps.variables.outputs.python_version }} terraform_version: ${{ steps.variables.outputs.terraform_version }} version: ${{ steps.variables.outputs.version }} - does_pull_request_exist: ${{ steps.pr_exists.outputs.does_pull_request_exist }} - branch_name: ${{ steps.variables.outputs.branch_name }} steps: - - name: "Checkout code" + - name: 'Checkout code' uses: actions/checkout@v6 - - name: "Set CI/CD variables" + - name: 'Set CI/CD variables' id: variables - env: - BRANCH_NAME: ${{ github.head_ref }} run: | datetime=$(date -u +'%Y-%m-%dT%H:%M:%S%z') - BUILD_DATETIME=$datetime make version-create-effective-file echo "build_datetime_london=$(TZ=Europe/London date --date=$datetime +'%Y-%m-%dT%H:%M:%S%z')" >> $GITHUB_OUTPUT echo "build_datetime=$datetime" >> $GITHUB_OUTPUT echo "build_timestamp=$(date --date=$datetime -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT echo "build_epoch=$(date --date=$datetime -u +'%s')" >> $GITHUB_OUTPUT - echo "nodejs_version=$(grep "^nodejs\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "python_version=$(grep "^python\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "terraform_version=$(grep "^terraform\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "version=$(head -n 1 .version 2> /dev/null || echo unknown)" >> $GITHUB_OUTPUT - echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT - - name: "Check if pull request exists for this branch" - id: pr_exists - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - branch_name=${GITHUB_HEAD_REF:-$(echo $GITHUB_REF | sed 's#refs/heads/##')} - echo "Current branch is '$branch_name'" - if gh pr list --head $branch_name | grep -q .; then - echo "Pull request exists" - echo "does_pull_request_exist=true" >> $GITHUB_OUTPUT - else - echo "Pull request doesn't exist" - echo "does_pull_request_exist=false" >> $GITHUB_OUTPUT - fi - - name: "List variables" - run: | - export BUILD_DATETIME_LONDON="${{ steps.variables.outputs.build_datetime_london }}" - export BUILD_DATETIME="${{ steps.variables.outputs.build_datetime }}" - export BUILD_TIMESTAMP="${{ steps.variables.outputs.build_timestamp }}" - export BUILD_EPOCH="${{ steps.variables.outputs.build_epoch }}" - export NODEJS_VERSION="${{ steps.variables.outputs.nodejs_version }}" - export PYTHON_VERSION="${{ steps.variables.outputs.python_version }}" - export TERRAFORM_VERSION="${{ steps.variables.outputs.terraform_version }}" - export VERSION="${{ steps.variables.outputs.version }}" - export DOES_PULL_REQUEST_EXIST="${{ steps.pr_exists.outputs.does_pull_request_exist }}" - export BRANCH_NAME="${{ steps.variables.outputs.branch_name }}" - make list-variables - commit-stage: # Recommended maximum execution time is 2 minutes - name: "Commit stage" + echo "nodejs_version=$(grep "^nodejs" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT + echo "python_version=$(grep "^python" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT + echo "terraform_version=$(grep "^terraform" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT + echo "version=${GITHUB_REF}" >> $GITHUB_OUTPUT + commit-stage: + name: 'Commit stage' needs: [metadata] uses: ./.github/workflows/stage-1-commit.yaml with: - build_datetime: "${{ needs.metadata.outputs.build_datetime }}" - build_timestamp: "${{ needs.metadata.outputs.build_timestamp }}" - build_epoch: "${{ needs.metadata.outputs.build_epoch }}" - nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}" - python_version: "${{ needs.metadata.outputs.python_version }}" - terraform_version: "${{ needs.metadata.outputs.terraform_version }}" - version: "${{ needs.metadata.outputs.version }}" + build_datetime: '${{ needs.metadata.outputs.build_datetime }}' + build_timestamp: '${{ needs.metadata.outputs.build_timestamp }}' + build_epoch: '${{ needs.metadata.outputs.build_epoch }}' + nodejs_version: '${{ needs.metadata.outputs.nodejs_version }}' + python_version: '${{ needs.metadata.outputs.python_version }}' + terraform_version: '${{ needs.metadata.outputs.terraform_version }}' + version: '${{ needs.metadata.outputs.version }}' secrets: inherit - test-stage: # Recommended maximum execution time is 5 minutes - name: "Test stage" - needs: [metadata, commit-stage] + test-stage: + name: 'Test stage' + needs: [metadata] uses: ./.github/workflows/stage-2-test.yaml with: - build_datetime: "${{ needs.metadata.outputs.build_datetime }}" - build_timestamp: "${{ needs.metadata.outputs.build_timestamp }}" - build_epoch: "${{ needs.metadata.outputs.build_epoch }}" - nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}" - python_version: "${{ needs.metadata.outputs.python_version }}" - terraform_version: "${{ needs.metadata.outputs.terraform_version }}" - version: "${{ needs.metadata.outputs.version }}" + build_datetime: '${{ needs.metadata.outputs.build_datetime }}' + build_timestamp: '${{ needs.metadata.outputs.build_timestamp }}' + build_epoch: '${{ needs.metadata.outputs.build_epoch }}' + nodejs_version: '${{ needs.metadata.outputs.nodejs_version }}' + python_version: '${{ needs.metadata.outputs.python_version }}' + terraform_version: '${{ needs.metadata.outputs.terraform_version }}' + version: '${{ needs.metadata.outputs.version }}' secrets: inherit - build-stage: # Recommended maximum execution time is 3 minutes - name: "Build stage" - needs: [metadata, test-stage] + build-stage: + name: 'Build stage' + needs: [metadata] uses: ./.github/workflows/stage-3-build.yaml - if: needs.metadata.outputs.does_pull_request_exist == 'true' || (github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')) with: - build_datetime: "${{ needs.metadata.outputs.build_datetime }}" - build_timestamp: "${{ needs.metadata.outputs.build_timestamp }}" - build_epoch: "${{ needs.metadata.outputs.build_epoch }}" - nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}" - python_version: "${{ needs.metadata.outputs.python_version }}" - terraform_version: "${{ needs.metadata.outputs.terraform_version }}" - version: "${{ needs.metadata.outputs.version }}" + build_datetime: '${{ needs.metadata.outputs.build_datetime }}' + build_timestamp: '${{ needs.metadata.outputs.build_timestamp }}' + build_epoch: '${{ needs.metadata.outputs.build_epoch }}' + nodejs_version: '${{ needs.metadata.outputs.nodejs_version }}' + python_version: '${{ needs.metadata.outputs.python_version }}' + terraform_version: '${{ needs.metadata.outputs.terraform_version }}' + version: '${{ needs.metadata.outputs.version }}' + commit_sha: '${{ github.event.pull_request.head.sha }}' secrets: inherit - acceptance-stage: # Recommended maximum execution time is 10 minutes - name: "Acceptance stage" - needs: [metadata, build-stage] - uses: ./.github/workflows/stage-4-acceptance.yaml - if: needs.metadata.outputs.does_pull_request_exist == 'true' || (github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')) + deploy-stage: + if: contains(github.event.pull_request.labels.*.name, 'deploy') + name: Deploy review app pr-${{ github.event.pull_request.number }} + needs: [build-stage] + permissions: + id-token: write + uses: ./.github/workflows/stage-4-deploy.yaml with: - build_datetime: "${{ needs.metadata.outputs.build_datetime }}" - build_timestamp: "${{ needs.metadata.outputs.build_timestamp }}" - build_epoch: "${{ needs.metadata.outputs.build_epoch }}" - nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}" - python_version: "${{ needs.metadata.outputs.python_version }}" - terraform_version: "${{ needs.metadata.outputs.terraform_version }}" - version: "${{ needs.metadata.outputs.version }}" + environments: '["review"]' + commit_sha: ${{ github.event.pull_request.head.sha }} + pr_number: ${{ github.event.pull_request.number }} secrets: inherit + post-url: + if: contains(github.event.pull_request.labels.*.name, 'deploy') + name: Post URL pr-${{ github.event.pull_request.number }} to PR comments + runs-on: ubuntu-latest + needs: [deploy-stage] + permissions: + pull-requests: write + steps: + - name: Post URL to PR comments + uses: marocchino/sticky-pull-request-comment@5060d4700a91de252c87eeddd2da026382d9298a + with: + message: | + The review app is available at this URL: + https://pr-${{ github.event.pull_request.number }}.manage-breast-screening.non-live.screening.nhs.uk + You must authenticate with HTTP basic authentication. Ask the team for credentials. diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml deleted file mode 100644 index 44bf0643..00000000 --- a/.github/workflows/stage-4-acceptance.yaml +++ /dev/null @@ -1,172 +0,0 @@ -name: "Acceptance stage" - -on: - workflow_call: - inputs: - build_datetime: - description: "Build datetime, set by the CI/CD pipeline workflow" - required: true - type: string - build_timestamp: - description: "Build timestamp, set by the CI/CD pipeline workflow" - required: true - type: string - build_epoch: - description: "Build epoch, set by the CI/CD pipeline workflow" - required: true - type: string - nodejs_version: - description: "Node.js version, set by the CI/CD pipeline workflow" - required: true - type: string - python_version: - description: "Python version, set by the CI/CD pipeline workflow" - required: true - type: string - terraform_version: - description: "Terraform version, set by the CI/CD pipeline workflow" - required: true - type: string - version: - description: "Version of the software, set by the CI/CD pipeline workflow" - required: true - type: string - -jobs: - environment-set-up: - name: "Environment set up" - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Create infractructure" - run: | - echo "Creating infractructure..." - - name: "Update database" - run: | - echo "Updating database..." - - name: "Deploy application" - run: | - echo "Deploying application..." - test-contract: - name: "Contract test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run contract test" - run: | - make test-contract - - name: "Save result" - run: | - echo "Nothing to save" - test-security: - name: "Security test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run security test" - run: | - make test-security - - name: "Save result" - run: | - echo "Nothing to save" - test-ui: - name: "UI test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run UI test" - run: | - cp .env.example .env - make test-ui - - name: "Save result" - run: | - echo "Nothing to save" - test-ui-performance: - name: "UI performance test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run UI performance test" - run: | - make test-ui-performance - - name: "Save result" - run: | - echo "Nothing to save" - test-integration: - name: "Integration test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run integration test" - run: | - make test-integration - - name: "Save result" - run: | - echo "Nothing to save" - test-accessibility: - name: "Accessibility test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run accessibility test" - run: | - cp .env.example .env - make test-accessibility - - name: "Save result" - run: | - echo "Nothing to save" - test-load: - name: "Load test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Run load tests" - run: | - make test-load - - name: "Save result" - run: | - echo "Nothing to save" - environment-tear-down: - name: "Environment tear down" - runs-on: ubuntu-latest - needs: - [ - test-accessibility, - test-contract, - test-integration, - test-load, - test-security, - test-ui-performance, - test-ui, - ] - if: always() - timeout-minutes: 5 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - - name: "Tear down environment" - run: | - echo "Tearing down environment..." diff --git a/.github/workflows/stage-4-deploy.yaml b/.github/workflows/stage-4-deploy.yaml new file mode 100644 index 00000000..8f25d64d --- /dev/null +++ b/.github/workflows/stage-4-deploy.yaml @@ -0,0 +1,68 @@ +name: Deployment stage + +on: + workflow_call: + inputs: + environments: + description: List of environments to deploy to (String array) + required: true + type: string + commit_sha: + description: Commit SHA used to fetch ADO pipeline and docker image + required: true + type: string + pr_number: + description: Pull request number when used in a pull request + required: false + type: string + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + strategy: + matrix: + environment: ${{ fromJson(inputs.environments) }} + max-parallel: 1 + environment: ${{ matrix.environment }} + # Prevent concurrent jobs on the same environment and between deploy and delete workflows + concurrency: deploy-${{ matrix.environment }}-${{inputs.pr_number}} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Call deployment pipeline + run: | + if [[ -n "${{inputs.pr_number}}" ]]; then + pr_argument="prNumber=${{inputs.pr_number}}" + else + pr_argument="" + fi + + source infrastructure/environments/${{ matrix.environment }}/variables.sh + + echo "Starting Azure devops pipeline \"Deploy to Azure - ${{ matrix.environment }}\"..." + RUN_ID=$(az pipelines run \ + --commit-id ${{inputs.commit_sha}} \ + --name "Deploy to Azure - ${{ matrix.environment }}" \ + --org https://dev.azure.com/nhse-dtos \ + --project lung-cancer-risk-check \ + --parameters commitSHA=${{inputs.commit_sha}} ${pr_argument} environment=${{ matrix.environment }} pool=${ADO_MANAGEMENT_POOL} \ + --output tsv --query id) + + echo "See pipeline run in Azure devops: https://dev.azure.com/nhse-dtos/lung-cancer-risk-check/_build/results?buildId=${RUN_ID}&view=results" + + scripts/bash/wait_ado_pipeline.sh "$RUN_ID" https://dev.azure.com/nhse-dtos lung-cancer-risk-check + + - name: Basic application smoke test + run: | + dns_zone_name=$( grep dns_zone_name infrastructure/environments/${{ matrix.environment }}/variables.tfvars | awk -F'"' '{print $2}' ) + use_apex_domain=$( grep use_apex_domain infrastructure/environments/${{ matrix.environment }}/variables.tfvars | awk '{print $3}' || echo "false" ) + scripts/bash/container_app_smoke_test.sh "${{ matrix.environment }}" "${{ inputs.commit_sha }}" "${dns_zone_name}" "${{inputs.pr_number}}" "${use_apex_domain}" From 5bb926185656ca07de9988f153e0d8d510f9b607 Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Mon, 16 Feb 2026 11:00:55 +0000 Subject: [PATCH 11/11] adding missing stuff --- .github/workflows/cicd-2-main-branch.yaml | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cicd-2-main-branch.yaml b/.github/workflows/cicd-2-main-branch.yaml index 959fb9cf..35a9fbc5 100644 --- a/.github/workflows/cicd-2-main-branch.yaml +++ b/.github/workflows/cicd-2-main-branch.yaml @@ -89,6 +89,7 @@ jobs: python_version: "${{ needs.metadata.outputs.python_version }}" terraform_version: "${{ needs.metadata.outputs.terraform_version }}" version: "${{ needs.metadata.outputs.version }}" + commit_sha: '${{ github.sha }}' secrets: inherit deploy-stage: @@ -98,20 +99,6 @@ jobs: id-token: write uses: ./.github/workflows/stage-4-deploy.yaml with: - environments: '["dev"]' + environments: '["review","dev"]' commit_sha: ${{ github.sha }} secrets: inherit - - acceptance-stage: # Recommended maximum execution time is 10 minutes - name: "Acceptance stage" - needs: [metadata, build-stage] - uses: ./.github/workflows/stage-4-acceptance.yaml - with: - build_datetime: "${{ needs.metadata.outputs.build_datetime }}" - build_timestamp: "${{ needs.metadata.outputs.build_timestamp }}" - build_epoch: "${{ needs.metadata.outputs.build_epoch }}" - nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}" - python_version: "${{ needs.metadata.outputs.python_version }}" - terraform_version: "${{ needs.metadata.outputs.terraform_version }}" - version: "${{ needs.metadata.outputs.version }}" - secrets: inherit