diff --git a/docs/deployment/runbooks/onboard-hospital-vm.md b/docs/deployment/runbooks/onboard-hospital-vm.md index 95d66134..62182602 100644 --- a/docs/deployment/runbooks/onboard-hospital-vm.md +++ b/docs/deployment/runbooks/onboard-hospital-vm.md @@ -13,7 +13,6 @@ - `*.his.arc.azure.com` - `relay-manbrs-.servicebus.windows.net` - [ ] Trust ODS code confirmed via the [ODS portal](https://odsportal.nhsbsa.nhs.uk/) -- [ ] PACS vendor confirmed (`sectra` | `fujifilm` | `agfa` | `philips` | `carestream`) - [ ] NHS region confirmed (`nw` | `neyh` | `mids` | `eoe` | `lon` | `se` | `sw`) - [ ] Deployment ring agreed with the programme team - [ ] `arc-onboarding-spn-client-id` and `arc-onboarding-spn-client-secret` retrieved from Key Vault @@ -22,15 +21,26 @@ ## Step 1 — Determine site parameters +The Arc resource name is built automatically from `SiteName`, `ODSCode`, and `Instance`: + +```text +gw--- +``` + +All lowercase, hyphens only. Azure constraint: max 54 characters (`a-z A-Z 0-9 - _ .`). + | Parameter | Format | Example | |-----------|--------|---------| -| `SiteCode` | `gw--` | `gw-RVJ-01` | -| `SiteName` | Trust name, hyphen-separated, no spaces | `North-Bristol-NHS-Trust` | -| `NHSRegion` | One of: `nw` `neyh` `mids` `eoe` `lon` `se` `sw` | `sw` | -| `PacsVendor` | One of: `sectra` `fujifilm` `agfa` `philips` `carestream` | `sectra` | +| `SiteName` | Trust name, hyphen-separated, no spaces | `Hull-University-Teaching-Hospitals-NHS-Trust` | +| `ODSCode` | ODS code (uppercase input, lowercased in name) | `RWA` | +| `Instance` | Zero-padded instance number | `01` | +| `NHSRegion` | One of: `nw` `neyh` `mids` `eoe` `lon` `se` `sw` | `neyh` | | `SiteType` | `static` or `mobile` | `static` | | `DeploymentRing` | `ring1`–`ring4` (see below) | `ring1` | +> **Example**: `SiteName=Hull-University-Teaching-Hospitals-NHS-Trust`, `ODSCode=RWA`, `Instance=01` +> → Arc resource name: `gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` (54 chars — at the limit) + **Ring assignments:** | Ring | Sites | @@ -43,54 +53,66 @@ ## Step 2 — Run Arc onboarding script on the gateway VM -Copy `scripts/powershell/arc-setup.ps1` to the VM and run from an **elevated PowerShell session**: +Copy [`scripts/powershell/arc-setup.ps1`](../../../scripts/powershell/arc-setup.ps1) to the VM and run from an **elevated PowerShell session**. + +If script execution is disabled on the VM, allow it for the current session first: + +```powershell +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process +``` + +> This only affects the current PowerShell process and does not change the machine-wide policy. +> If the script was downloaded from the internet and is still blocked, see [Script execution blocked](#script-execution-blocked) in Troubleshooting. ```powershell .\arc-setup.ps1 ` -SubscriptionId "" ` -TenantId "" ` - -ResourceGroup "rg-manbgw--uks-arc-enabled-servers" ` + -ResourceGroup "rg-mbsgw--uks-arc-enabled-servers" ` -Location "uksouth" ` -ServicePrincipalId "" ` -ServicePrincipalSecret "" ` - -SiteCode "gw-RVJ-01" ` - -SiteName "North-Bristol-NHS-Trust" ` - -NHSRegion "sw" ` - -PacsVendor "sectra" ` + -SiteName "Hull-University-Teaching-Hospitals-NHS-Trust" ` + -ODSCode "RWA" ` + -Instance "01" ` + -NHSRegion "neyh" ` -SiteType "static" ` -DeploymentRing "ring1" ``` The script will: + 1. Install the Azure Arc agent (`azcmagent`) if not already present -2. Stamp site metadata as tags on the Arc machine resource -3. Connect the VM to Azure Arc with `--resource-name` set to `SiteCode` +2. Build the Arc resource name: `gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` +3. Stamp site metadata as tags on the Arc machine resource +4. Connect the VM to Azure Arc with `--resource-name` set to the built resource name Logs are written to `C:\ArcSetup\ArcSetup.log`. -**Verify**: In the Azure portal, navigate to `rg-manbgw--uks-arc-enabled-servers` → Azure Arc machines → `gw-RVJ-01`. Status should be **Connected**. +**Verify**: In the Azure portal, navigate to `rg-mbsgw--uks-arc-enabled-servers` → Azure Arc machines → `gw-hull-university-teaching-hospitals-nhs-trust-rwa-01`. Status should be **Connected**. ## Step 3 — Trigger Terraform to provision the Hybrid Connection Run the ADO pipeline **Deploy Arc Infrastructure - \** manually. Terraform discovers the new Arc machine and creates: -- `hc-gw-RVJ-01` in the relay namespace (`relay-manbrs-`) +- `hc-gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` in the relay namespace (`relay-manbrs-`) - `listen` auth rule on that Hybrid Connection -**Verify**: In the Azure portal, navigate to `relay-manbrs-` → Hybrid Connections → `hc-gw-RVJ-01` is present. +**Verify**: In the Azure portal, navigate to `relay-manbrs-` → Hybrid Connections → `hc-gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` is present. ## Step 4 — Deploy the gateway application Run the ADO pipeline **Deploy Gateway - \** with: -``` -targetSiteCode : gw-RVJ-01 +```text +targetSiteCode : gw-hull-university-teaching-hospitals-nhs-trust-rwa-01 releaseTag : latest (or a specific tag, e.g. v1.2.3) ``` The pipeline: -1. Retrieves the listen SAS key for `hc-gw-RVJ-01` -2. Sends an Arc Run Command to `gw-RVJ-01` that writes `.env` and runs `deploy.ps1` + +1. Retrieves the listen SAS key for `hc-gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` +2. Sends an Arc Run Command to `gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` that writes `.env` and runs `deploy.ps1` 3. Polls for completion and reports success or failure ## Step 5 — Smoke test @@ -110,17 +132,17 @@ Check Log Analytics Workspace for an initial heartbeat within 5 minutes of servi ## Parameters reference | Parameter | Required | Default | Description | -|-----------|----------|---------|-------------| +| --------- | -------- | ------- | ----------- | | `-SubscriptionId` | Yes | — | Azure spoke subscription ID | | `-TenantId` | Yes | — | Azure Entra tenant ID | | `-ResourceGroup` | Yes | — | Arc-enabled servers resource group | | `-Location` | Yes | — | Azure region (always `uksouth`) | | `-ServicePrincipalId` | Yes | — | Arc onboarding SPN client ID | | `-ServicePrincipalSecret` | Yes | — | Arc onboarding SPN client secret | -| `-SiteCode` | No | *(hostname)* | Arc resource name and tag; format `gw--` | -| `-SiteName` | No | *(not set)* | Human-readable trust name; no spaces | +| `-SiteName` | No | *(hostname)* | Trust name, hyphen-separated, no spaces; used to build Arc resource name | +| `-ODSCode` | No | *(hostname)* | ODS code; used to build Arc resource name | +| `-Instance` | No | `01` | Zero-padded instance number; used to build Arc resource name | | `-NHSRegion` | No | *(not set)* | NHS region code | -| `-PacsVendor` | No | *(not set)* | PACS system vendor | | `-SiteType` | No | `static` | `static` or `mobile` | | `-DeploymentRing` | No | `ring0` | Rollout ring (`ring0`–`ring4`) | @@ -136,6 +158,26 @@ Check `C:\ArcSetup\ArcSetup.log` on the VM. Common causes: - **SPN credentials wrong** — verify client ID and secret from Key Vault are current - **VM already registered** — if the machine was previously connected under a different name, disconnect first: `azcmagent disconnect` +### Script execution blocked + +If you see `running scripts is disabled on this system`, run this first in the same elevated session: + +```powershell +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process +``` + +If the script was downloaded from the internet and is still blocked (error: `file is not digitally signed`), unblock the zone restriction first: + +```powershell +Unblock-File -Path .\arc-setup.ps1 +``` + +If the script is still blocked after `Unblock-File` (e.g. due to a stricter machine policy), use `Unrestricted` instead — still scoped to the current process only: + +```powershell +Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process +``` + ### Arc machine shows as Disconnected after onboarding The agent may have lost connectivity. Check: diff --git a/infrastructure/modules/arc-infra/outputs.tf b/infrastructure/modules/arc-infra/outputs.tf index e0f7b0c7..4173dbe8 100644 --- a/infrastructure/modules/arc-infra/outputs.tf +++ b/infrastructure/modules/arc-infra/outputs.tf @@ -29,7 +29,7 @@ output "relay_namespace_hostname" { } output "relay_listen_sas_keys" { - description = "Per-machine relay listen SAS primary keys, keyed by Arc resource name (SiteCode). Used by the deploy pipeline to write .env files." + description = "Per-machine relay listen SAS primary keys, keyed by Arc resource name. Used by the deploy pipeline to write .env files." sensitive = true value = var.enable_arc_servers ? { for k, rule in azurerm_relay_hybrid_connection_authorization_rule.per_machine_listen : diff --git a/infrastructure/modules/arc-infra/relay.tf b/infrastructure/modules/arc-infra/relay.tf index 69aee96d..5c45d042 100644 --- a/infrastructure/modules/arc-infra/relay.tf +++ b/infrastructure/modules/arc-infra/relay.tf @@ -1,7 +1,8 @@ # The relay namespace is owned by dtos-manage-breast-screening ("manbrs"). # This module creates one Hybrid Connection + listen-only auth rule per Arc-enabled # machine, auto-discovered by querying the Arc resource group. -# HC names are derived from the Arc resource name (= SiteCode set at onboarding). +# HC names are derived from the Arc resource name set at onboarding +# (e.g. hc-gw-hull-university-teaching-hospitals-nhs-trust-rwa-01). # # Trigger: run `terraform apply` after each Arc onboarding to pick up new machines. @@ -11,7 +12,8 @@ locals { } # Discover all Arc-enabled machines registered in the Arc resource group. -# Each machine's name is the SiteCode set during onboarding (e.g. gw-RVJ-01). +# Each machine's Arc resource name is set during onboarding +# (e.g. gw-hull-university-teaching-hospitals-nhs-trust-rwa-01). data "azurerm_resources" "arc_machines" { count = var.enable_arc_servers ? 1 : 0 @@ -33,7 +35,7 @@ locals { arc_machines = merge(local.arc_machines_discovered, local.arc_machines_static) } -# One Hybrid Connection per Arc machine (e.g. hc-gw-RVJ-01). +# One Hybrid Connection per Arc machine (e.g. hc-gw-hull-university-teaching-hospitals-nhs-trust-rwa-01). resource "azurerm_relay_hybrid_connection" "per_machine" { for_each = local.arc_machines diff --git a/infrastructure/modules/gateway-test-vm/vm.tf b/infrastructure/modules/gateway-test-vm/vm.tf index 451a5294..f3fab2ca 100644 --- a/infrastructure/modules/gateway-test-vm/vm.tf +++ b/infrastructure/modules/gateway-test-vm/vm.tf @@ -125,11 +125,8 @@ resource "azurerm_virtual_machine_run_command" "arc_setup" { # Site identity tags — stamped onto the Arc resource for Terraform HC discovery # and ADO pipeline ring targeting. ring0 = test VM only. - parameter { - name = "SiteCode" - value = "${var.app_short_name}-${var.env_config}" - } - + # SiteName/ODSCode/Instance are not passed; the script falls back to the machine + # hostname (computer_name = mbsgw-) as the Arc resource name. parameter { name = "SiteType" value = "static" diff --git a/infrastructure/terraform/outputs.tf b/infrastructure/terraform/outputs.tf index 27b0703d..cb474a26 100644 --- a/infrastructure/terraform/outputs.tf +++ b/infrastructure/terraform/outputs.tf @@ -4,7 +4,7 @@ output "relay_namespace_hostname" { } output "relay_listen_sas_keys" { - description = "Per-machine relay listen SAS primary keys, keyed by Arc resource name (SiteCode)" + description = "Per-machine relay listen SAS primary keys, keyed by Arc resource name" sensitive = true value = module.arc_infra.relay_listen_sas_keys } diff --git a/scripts/powershell/arc-setup.ps1 b/scripts/powershell/arc-setup.ps1 index d2b229c1..6c410a84 100644 --- a/scripts/powershell/arc-setup.ps1 +++ b/scripts/powershell/arc-setup.ps1 @@ -8,12 +8,13 @@ param( [string]$ServicePrincipalId, [string]$ServicePrincipalSecret, # Site identity - controls the Arc resource name and Azure tags. - # SiteCode becomes the Arc machine name in Azure (e.g. gw-RVJ-01). - # Defaults to the machine hostname when not supplied (test/dev use). - [string]$SiteCode = "", # e.g. gw-RVJ-01 (ODS code + instance) + # Resource name is built as gw--- (all lowercase). + # e.g. gw-north-bristol-nhs-trust-rvj-01 + # Defaults to the machine hostname when SiteName/ODSCode/Instance are not supplied (test/dev use). [string]$SiteName = "", # e.g. North-Bristol-NHS-Trust (no spaces) + [string]$ODSCode = "", # e.g. RVJ + [string]$Instance = "01", # zero-padded instance number [string]$NHSRegion = "", # nw|neyh|mids|eoe|lon|se|sw - [string]$PacsVendor = "", # sectra|fujifilm|agfa|philips|carestream [string]$SiteType = "static", # static|mobile [string]$DeploymentRing = "ring0" # ring0|ring1|ring2|ring3|ring4 ) @@ -35,10 +36,10 @@ try { Write-Log "=========================================" "INFO" Write-Log "Azure Arc Combined Setup Started" "INFO" Write-Log "=========================================" "INFO" - Write-Log "SiteCode : $(if ($SiteCode) { $SiteCode } else { '(hostname)' })" "INFO" Write-Log "SiteName : $(if ($SiteName) { $SiteName } else { '(not set)' })" "INFO" + Write-Log "ODSCode : $(if ($ODSCode) { $ODSCode } else { '(not set)' })" "INFO" + Write-Log "Instance : $Instance" "INFO" Write-Log "NHSRegion : $(if ($NHSRegion) { $NHSRegion } else { '(not set)' })" "INFO" - Write-Log "PacsVendor : $(if ($PacsVendor) { $PacsVendor } else { '(not set)' })" "INFO" Write-Log "SiteType : $SiteType" "INFO" Write-Log "DeploymentRing : $DeploymentRing" "INFO" Write-Log "=========================================" "INFO" @@ -142,16 +143,26 @@ try { Write-Log "Resource Group: $ResourceGroup" "INFO" Write-Log "Location : $Location" "INFO" + # Build the Arc resource name from structured inputs (all lowercase). + # Format: gw---, e.g. gw-hull-university-teaching-hospitals-nhs-trust-rwa-01 + # Azure constraint: a-z A-Z 0-9 - _ . and max 54 characters. + # Falls back to the machine hostname when SiteName/ODSCode are not supplied (test/dev use). + $ResourceName = if ($SiteName -and $ODSCode) { + "gw-$($SiteName.ToLower())-$($ODSCode.ToLower())-$Instance" + } else { "" } + + if ($ResourceName) { Write-Log "ResourceName : $ResourceName" "INFO" } + # Build tags - all site metadata is stamped onto the Arc resource for Terraform # discovery and ADO pipeline targeting. SiteName must not contain spaces. $tags = "ArcSQLServerExtensionDeployment=Disabled" $tags += ",Programme=BreastScreening" $tags += ",SiteType=$SiteType" $tags += ",DeploymentRing=$DeploymentRing" - if ($SiteCode) { $tags += ",SiteCode=$SiteCode" } - if ($SiteName) { $tags += ",SiteName=$SiteName" } - if ($NHSRegion) { $tags += ",NHSRegion=$NHSRegion" } - if ($PacsVendor) { $tags += ",PacsVendor=$PacsVendor" } + if ($SiteName) { $tags += ",SiteName=$SiteName" } + if ($ODSCode) { $tags += ",ODSCode=$ODSCode" } + if ($Instance) { $tags += ",Instance=$Instance" } + if ($NHSRegion) { $tags += ",NHSRegion=$NHSRegion" } # Build connect arguments. --resource-name sets the Azure resource name, # overriding the default (hostname). Required for meaningful HC naming in Terraform. @@ -167,7 +178,7 @@ try { '--correlation-id', $CorrelationId, '--tags', $tags ) - if ($SiteCode) { $connectArgs += @('--resource-name', $SiteCode) } + if ($ResourceName) { $connectArgs += @('--resource-name', $ResourceName) } & "$azcmagentExe" @connectArgs