Skip to content

Commit 544fc0a

Browse files
authored
Merge pull request #5575 from dfe-analytical-services/dev
Merge Dev into Master.
2 parents 6c98327 + 818f224 commit 544fc0a

File tree

177 files changed

+7333
-2865
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+7333
-2865
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ useful-scripts/get_data_block_responses/results*
7373

7474
src/GovUk.Education.ExploreEducationStatistics.Data.Seed/Files/
7575

76+
## Infrastructure build artifacts
77+
78+
infrastructure/templates/public-api/main.json
79+
7680
## Misc
7781

7882
*.~lock*

infrastructure/templates/public-api/abbreviations.bicep

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ var abbreviations = {
55
appManagedEnvironments: 'cae'
66
// TODO - remove the "-flexibleserver" suffix and change the suffix of our PSQL instance to "-01"
77
dBforPostgreSQLServers: 'psql-flexibleserver'
8-
// 'fs' is non-standard - it should be 'share'
9-
fileShare: 'fs'
8+
fileShare: 'share'
109
// 'ai' is non-standard - it should be 'appi'
1110
insightsComponents: 'ai'
1211
managedIdentityUserAssignedIdentities: 'id'

infrastructure/templates/public-api/application/public-api/publicApiDataProcessor.bicep

+6-3
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ resource outboundVnetSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-0
6262
parent: vNet
6363
}
6464

65-
resource inboundVnetSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
65+
resource privateEndpointsSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
6666
name: resourceNames.existingResources.subnets.dataProcessorPrivateEndpoints
6767
parent: vNet
6868
}
6969

7070
// Deploy Data Processor Function.
71-
module dataProcessorFunctionAppModule '../../components/functionApp.bicep' = {
71+
module dataProcessorFunctionAppModule '../../components/durableFunctionApp.bicep' = {
7272
name: 'dataProcessorFunctionAppDeploy'
7373
params: {
7474
functionAppName: resourceNames.publicApi.dataProcessor
@@ -77,7 +77,10 @@ module dataProcessorFunctionAppModule '../../components/functionApp.bicep' = {
7777
location: location
7878
applicationInsightsKey: applicationInsightsKey
7979
subnetId: outboundVnetSubnet.id
80-
privateEndpointSubnetId: inboundVnetSubnet.id
80+
privateEndpoints: {
81+
functionApp: privateEndpointsSubnet.id
82+
storageAccounts: privateEndpointsSubnet.id
83+
}
8184
publicNetworkAccessEnabled: true
8285
functionAppFirewallRules: functionAppFirewallRules
8386
entraIdAuthentication: {

infrastructure/templates/public-api/application/public-api/publicApiStorage.bicep

+14-26
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { ResourceNames, IpRange } from '../../types.bicep'
1+
import { ResourceNames, IpRange, PublicApiStorageAccountConfig } from '../../types.bicep'
22

33
@description('Specifies common resource naming variables.')
44
param resourceNames ResourceNames
55

66
@description('Specifies the location for all resources.')
77
param location string
88

9-
@description('Public API Storage : Size of the file share in GB.')
10-
param publicApiDataFileShareQuotaGbs int
9+
@description('Public API storage account and file share configuration.')
10+
param config PublicApiStorageAccountConfig
1111

12-
@description('Public API Storage : Firewall rules.')
12+
@description('Firewall rules.')
1313
param storageFirewallRules IpRange[]
1414

1515
@description('Specifies a set of tags with which to tag the resource in Azure.')
@@ -22,41 +22,29 @@ resource vNet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = {
2222
name: resourceNames.existingResources.vNet
2323
}
2424

25-
resource dataProcessorSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
26-
name: resourceNames.existingResources.subnets.dataProcessor
25+
resource storagePrivateEndpointSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
26+
name: resourceNames.existingResources.subnets.storagePrivateEndpoints
2727
parent: vNet
2828
}
2929

30-
resource containerAppEnvironmentSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
31-
name: resourceNames.existingResources.subnets.containerAppEnvironment
32-
parent: vNet
33-
}
34-
35-
resource publisherSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
36-
name: resourceNames.existingResources.subnets.publisherFunction
37-
parent: vNet
38-
}
39-
40-
// TODO EES-5128 - add private endpoints to allow VNet traffic to go directly to Storage Account over the VNet.
41-
// Currently supported by subnet whitelisting and Storage service endpoints being enabled on the whitelisted subnets.
4230
module publicApiStorageAccountModule '../../components/storageAccount.bicep' = {
4331
name: 'publicApiStorageAccountDeploy'
4432
params: {
4533
location: location
4634
storageAccountName: resourceNames.publicApi.publicApiStorageAccount
47-
allowedSubnetIds: [
48-
dataProcessorSubnet.id
49-
containerAppEnvironmentSubnet.id
50-
publisherSubnet.id
51-
]
35+
publicNetworkAccessEnabled: false
5236
firewallRules: storageFirewallRules
53-
skuStorageResource: 'Standard_LRS'
37+
sku: config.sku
38+
kind: config.kind
5439
keyVaultName: resourceNames.existingResources.keyVault
5540
alerts: deployAlerts ? {
5641
availability: true
5742
latency: true
5843
alertsGroupName: resourceNames.existingResources.alertsGroup
5944
} : null
45+
privateEndpointSubnetIds: {
46+
file: storagePrivateEndpointSubnet.id
47+
}
6048
tagValues: tagValues
6149
}
6250
}
@@ -65,9 +53,9 @@ module dataFilesFileShareModule '../../components/fileShare.bicep' = {
6553
name: 'fileShareDeploy'
6654
params: {
6755
fileShareName: resourceNames.publicApi.publicApiFileShare
68-
fileShareQuotaGbs: publicApiDataFileShareQuotaGbs
56+
fileShareQuotaGbs: config.fileShare.quotaGbs
6957
storageAccountName: publicApiStorageAccountModule.outputs.storageAccountName
70-
fileShareAccessTier: 'TransactionOptimized'
58+
fileShareAccessTier: config.fileShare.accessTier
7159
alerts: deployAlerts ? {
7260
availability: true
7361
latency: true

infrastructure/templates/public-api/application/shared/postgreSqlFlexibleServer.bicep

+5-29
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ResourceNames, IpRange, PrincipalNameAndId } from '../../types.bicep'
1+
import { ResourceNames, IpRange, PrincipalNameAndId, PostgreSqlFlexibleServerConfig } from '../../types.bicep'
22

33
@description('Specifies common resource naming variables.')
44
param resourceNames ResourceNames
@@ -13,14 +13,8 @@ param adminName string
1313
@secure()
1414
param adminPassword string
1515

16-
@description('SKU name.')
17-
param sku string = 'Standard_B1ms'
18-
19-
@description('Storage Size in GB.')
20-
param storageSizeGB int = 32
21-
22-
@description('Autogrow setting.')
23-
param autoGrowStatus string = 'Disabled'
16+
@description('Server configuration.')
17+
param serverConfig PostgreSqlFlexibleServerConfig
2418

2519
@description('Firewall rules.')
2620
param firewallRules IpRange[] = []
@@ -31,9 +25,6 @@ param privateEndpointSubnetId string
3125
@description('An array of Entra ID admin principal names for this resource')
3226
param entraIdAdminPrincipals PrincipalNameAndId[] = []
3327

34-
@description('Whether backups will be geo-redundant rather than zone-redundant')
35-
param geoRedundantBackupEnabled bool
36-
3728
@description('Whether to create or update Azure Monitor alerts during this deploy')
3829
param deployAlerts bool
3930

@@ -45,7 +36,7 @@ var formattedFirewallRules = map(firewallRules, rule => {
4536
cidr: rule.cidr
4637
})
4738

48-
module postgreSqlServerModule '../../components/postgresqlDatabase.bicep' = {
39+
module postgreSqlServerModule '../../components/postgreSqlFlexibleServer.bicep' = {
4940
name: 'postgreSQLDatabaseDeploy'
5041
params: {
5142
databaseServerName: resourceNames.sharedResources.postgreSqlFlexibleServer
@@ -54,14 +45,10 @@ module postgreSqlServerModule '../../components/postgresqlDatabase.bicep' = {
5445
adminName: adminName
5546
adminPassword: adminPassword
5647
entraIdAdminPrincipals: entraIdAdminPrincipals
57-
dbSkuName: sku
58-
dbStorageSizeGB: storageSizeGB
59-
dbAutoGrowStatus: autoGrowStatus
60-
postgreSqlVersion: '16'
48+
serverConfig: serverConfig
6149
firewallRules: formattedFirewallRules
6250
databaseNames: ['public_data']
6351
privateEndpointSubnetId: privateEndpointSubnetId
64-
geoRedundantBackup: geoRedundantBackupEnabled ? 'Enabled' : 'Disabled'
6552
alerts: deployAlerts ? {
6653
availability: true
6754
queryTime: true
@@ -80,17 +67,6 @@ module postgreSqlServerModule '../../components/postgresqlDatabase.bicep' = {
8067
}
8168
}
8269

83-
resource maxPreparedTransactionsConfig 'Microsoft.DBforPostgreSQL/flexibleServers/configurations@2022-12-01' = {
84-
name: '${resourceNames.sharedResources.postgreSqlFlexibleServer}/max_prepared_transactions'
85-
properties: {
86-
value: '100'
87-
source: 'user-override'
88-
}
89-
dependsOn: [
90-
postgreSqlServerModule
91-
]
92-
}
93-
9470
var managedIdentityConnectionStringTemplate = postgreSqlServerModule.outputs.managedIdentityConnectionStringTemplate
9571

9672
var dataProcessorPsqlConnectionStringSecretKey = 'ees-publicapi-data-processor-connectionstring-publicdatadb'

infrastructure/templates/public-api/application/shared/privateDnsZones.bicep

+38-4
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,42 @@ module sitesPrivateDnsZoneModule '../../components/privateDnsZone.bicep' = {
2727
}
2828
}
2929

30-
output postgreSqlPrivateDnsZoneId string = postgreSqlPrivateDnsZoneModule.outputs.privateDnsZoneId
31-
output postgreSqlPrivateDnsZoneName string = postgreSqlPrivateDnsZoneModule.outputs.privateDnsZoneName
30+
// Set up a Private DNS zone for handling private endpoints for Storage Account File Services.
31+
module fileServicePrivateDnsZoneModule '../../components/privateDnsZone.bicep' = {
32+
name: 'fileServicePrivateDnsZoneDeploy'
33+
params: {
34+
zoneType: 'fileService'
35+
vnetName: resourceNames.existingResources.vNet
36+
tagValues: tagValues
37+
}
38+
}
39+
40+
// Set up a Private DNS zone for handling private endpoints for Storage Account Blob Storage.
41+
module blobStoragePrivateDnsZoneModule '../../components/privateDnsZone.bicep' = {
42+
name: 'blobStoragePrivateDnsZoneDeploy'
43+
params: {
44+
zoneType: 'blobStorage'
45+
vnetName: resourceNames.existingResources.vNet
46+
tagValues: tagValues
47+
}
48+
}
3249

33-
output sitesPrivateDnsZoneId string = sitesPrivateDnsZoneModule.outputs.privateDnsZoneId
34-
output sitesPrivateDnsZoneName string = sitesPrivateDnsZoneModule.outputs.privateDnsZoneName
50+
// Set up a Private DNS zone for handling private endpoints for Storage Account Queues.
51+
module queuePrivateDnsZoneModule '../../components/privateDnsZone.bicep' = {
52+
name: 'queuePrivateDnsZoneDeploy'
53+
params: {
54+
zoneType: 'queue'
55+
vnetName: resourceNames.existingResources.vNet
56+
tagValues: tagValues
57+
}
58+
}
59+
60+
// Set up a Private DNS zone for handling private endpoints for Storage Account Table Storage.
61+
module tableStoragePrivateDnsZoneModule '../../components/privateDnsZone.bicep' = {
62+
name: 'tableStoragePrivateDnsZoneDeploy'
63+
params: {
64+
zoneType: 'tableStorage'
65+
vnetName: resourceNames.existingResources.vNet
66+
tagValues: tagValues
67+
}
68+
}

infrastructure/templates/public-api/application/shared/virtualNetwork.bicep

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ resource appGatewaySubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01'
4444
parent: vNet
4545
}
4646

47+
resource storageAccountPrivateEndpointSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
48+
name: subnets.storagePrivateEndpoints
49+
parent: vNet
50+
}
51+
4752
@description('The fully qualified Azure resource ID of the virtual network')
4853
output vnetId string = resourceId('Microsoft.Network/VirtualNetworks', resourceNames.existingResources.vNet)
4954

@@ -103,3 +108,6 @@ output psqlFlexibleServerSubnetEndIpAddress string = parseCidr(psqlFlexibleServe
103108

104109
@description('The fully qualified Azure resource ID of the App Gateway Subnet.')
105110
output appGatewaySubnetRef string = appGatewaySubnet.id
111+
112+
@description('The fully qualified Azure resource ID of the Public API Storage Account Private Endpoint Subnet.')
113+
output storageAccountPrivateEndpointSubnetRef string = storageAccountPrivateEndpointSubnet.id

infrastructure/templates/public-api/ci/jobs/deploy-data-processor.yml

+12-13
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- template: ../tasks/bicep-output-variables.yml
2525
parameters:
2626
serviceConnection: ${{ parameters.serviceConnection }}
27-
27+
2828
# We do config updates out of Bicep template so we can implement slot swapping.
2929
# Changes are first deployed to the staging slot and combined with a fresh
3030
# code deploy prior to being swapped with the production slot.
@@ -66,18 +66,17 @@ jobs:
6666
--settings \
6767
"[email protected](VaultName=$(keyVaultName); SecretName=$(dataProcessorPsqlConnectionStringSecretKey))"
6868
69-
# TODO EES-5128
70-
# Retry deploying the Function App in order to allow the staging slot the time to
71-
# fully restart after config and network settings have been updated prior to deploy.
72-
#
73-
# Deploying prematurely results in a 500 from the deployment endpoint until the
74-
# endpoint is ready to accept the deployment request.
75-
#
76-
# In the future it would be preferable to have a health check Function that can
77-
# check the site is ready, but we need to add the Service Principal to allowed
78-
# Client IDs / Identities that can access the Function App. The Service Principal
79-
# that is performing the deploy can be accessed by using the `addSpnToEnvironment`
80-
# config option in the task definition and using the $(servicePrincipalId) variable.
69+
- template: ../tasks/wait-for-endpoint-success.yml
70+
parameters:
71+
serviceConnection: ${{ parameters.serviceConnection }}
72+
displayName: Checking that staging slot is healthy after updating appsettings
73+
maxAttempts: 20
74+
accessTokenScope: $(dataProcessorAppRegistrationClientId)
75+
endpoint: $(dataProcessorFunctionAppStagingUrl)/api/HealthCheck
76+
# We allow 404s because if this is the first time the Function App is being deployed, there won't be any
77+
# deployed code yet in the staging slot.
78+
allow404s: true
79+
8180
- task: AzureCLI@2
8281
displayName: Deploy to staging slot
8382
retryCountOnTaskFailure: 10

infrastructure/templates/public-api/ci/jobs/deploy-infrastructure.yml

+6-15
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,12 @@ jobs:
4444
deployAlerts: false
4545
dataProcessorExists: false
4646

47-
- task: AzureCLI@2
48-
displayName: Check if Data Processor Function App exists
49-
inputs:
50-
azureSubscription: ${{ parameters.serviceConnection }}
51-
scriptType: bash
52-
scriptLocation: inlineScript
53-
inlineScript: |
54-
set -e
55-
dataProcessorExists=`az functionapp list --resource-group $(resourceGroupName) --query "[?name=='$(dataProcessorFunctionAppName)']" | jq '. != []'`
56-
57-
if [[ "$dataProcessorExists" == "true" ]]; then
58-
echo "Data Processor Function App exists"
59-
fi
60-
61-
echo "##vso[task.setvariable variable=dataProcessorExists;]$dataProcessorExists"
47+
- template: ../tasks/check-function-app-exists.yml
48+
parameters:
49+
serviceConnection: ${{ parameters.serviceConnection }}
50+
resourceGroupName: $(resourceGroupName)
51+
functionAppName: $(dataProcessorFunctionAppName)
52+
variableName: dataProcessorExists
6253

6354
- template: ../tasks/deploy-bicep.yml
6455
parameters:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
parameters:
2+
- name: serviceConnection
3+
type: string
4+
- name: resourceGroupName
5+
type: string
6+
- name: functionAppName
7+
type: string
8+
- name: variableName
9+
type: string
10+
11+
steps:
12+
- task: AzureCLI@2
13+
displayName: Check if ${{ parameters.functionAppName }} exists
14+
inputs:
15+
azureSubscription: ${{ parameters.serviceConnection }}
16+
scriptType: bash
17+
scriptLocation: inlineScript
18+
inlineScript: |
19+
set -e
20+
functionAppExists=`az functionapp list --resource-group ${{ parameters.resourceGroupName }} --query "[?name=='${{ parameters.functionAppName }}']" | jq '. != []'`
21+
22+
if [[ "$functionAppExists" == "true" ]]; then
23+
echo "${{ parameters.functionAppName }} exists"
24+
fi
25+
26+
echo "##vso[task.setvariable variable=${{ parameters.variableName }};]$functionAppExists"

0 commit comments

Comments
 (0)