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
90 changes: 66 additions & 24 deletions docs/deployment/runbooks/onboard-hospital-vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
- `*.his.arc.azure.com`
- `relay-manbrs-<env>.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
Expand All @@ -22,15 +21,26 @@

## Step 1 — Determine site parameters

The Arc resource name is built automatically from `SiteName`, `ODSCode`, and `Instance`:

```text
gw-<SiteName>-<ODSCode>-<Instance>
```

All lowercase, hyphens only. Azure constraint: max 54 characters (`a-z A-Z 0-9 - _ .`).

| Parameter | Format | Example |
|-----------|--------|---------|
| `SiteCode` | `gw-<ODSCode>-<instance>` | `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 |
Expand All @@ -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 "<spoke-subscription-id>" `
-TenantId "<tenant-id>" `
-ResourceGroup "rg-manbgw-<env>-uks-arc-enabled-servers" `
-ResourceGroup "rg-mbsgw-<env>-uks-arc-enabled-servers" `
-Location "uksouth" `
-ServicePrincipalId "<arc-onboarding-spn-client-id>" `
-ServicePrincipalSecret "<arc-onboarding-spn-client-secret>" `
-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-<env>-uks-arc-enabled-servers` → Azure Arc machines → `gw-RVJ-01`. Status should be **Connected**.
**Verify**: In the Azure portal, navigate to `rg-mbsgw-<env>-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 - \<env\>** manually. Terraform discovers the new Arc machine and creates:

- `hc-gw-RVJ-01` in the relay namespace (`relay-manbrs-<env>`)
- `hc-gw-hull-university-teaching-hospitals-nhs-trust-rwa-01` in the relay namespace (`relay-manbrs-<env>`)
- `listen` auth rule on that Hybrid Connection

**Verify**: In the Azure portal, navigate to `relay-manbrs-<env>` → Hybrid Connections → `hc-gw-RVJ-01` is present.
**Verify**: In the Azure portal, navigate to `relay-manbrs-<env>` → 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 - \<env\>** 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
Expand All @@ -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-<ODSCode>-<instance>` |
| `-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`) |

Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/modules/arc-infra/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand Down
8 changes: 5 additions & 3 deletions infrastructure/modules/arc-infra/relay.tf
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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

Expand All @@ -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

Expand Down
7 changes: 2 additions & 5 deletions infrastructure/modules/gateway-test-vm/vm.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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-<env>) as the Arc resource name.
parameter {
name = "SiteType"
value = "static"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/terraform/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
33 changes: 22 additions & 11 deletions scripts/powershell/arc-setup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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-<SiteName>-<ODSCode>-<Instance> (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
)
Expand All @@ -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"
Expand Down Expand Up @@ -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-<SiteName>-<ODSCode>-<Instance>, 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.
Expand All @@ -167,7 +178,7 @@ try {
'--correlation-id', $CorrelationId,
'--tags', $tags
)
if ($SiteCode) { $connectArgs += @('--resource-name', $SiteCode) }
if ($ResourceName) { $connectArgs += @('--resource-name', $ResourceName) }

& "$azcmagentExe" @connectArgs

Expand Down
Loading