File size: 5,433 Bytes
80db45a
 
 
 
 
 
 
 
 
 
 
f24be86
 
 
 
 
 
 
 
 
 
 
 
 
fb79ec6
 
 
 
 
 
 
80db45a
f24be86
 
 
b3a3223
 
 
 
80db45a
 
 
 
 
 
 
 
 
9595e1d
 
 
d609fe2
9595e1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3a3223
f24be86
 
 
 
fb79ec6
b3a3223
9595e1d
 
 
f24be86
9595e1d
 
 
 
f24be86
9595e1d
 
 
 
4dec878
9595e1d
 
 
 
 
5f87718
4dec878
f194cdb
fb79ec6
f24be86
4dec878
886a656
fb79ec6
9595e1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669c0b8
 
 
 
 
 
 
 
 
fb79ec6
 
 
 
 
 
 
 
f24be86
 
 
 
 
 
 
fb79ec6
f24be86
 
 
 
 
 
 
 
 
 
 
 
fb79ec6
 
 
 
 
 
 
 
 
 
 
aef59ad
 
 
 
 
 
 
 
 
fb79ec6
aef59ad
f24be86
9595e1d
 
 
 
 
 
 
 
 
 
f24be86
9595e1d
f24be86
 
80db45a
f24be86
 
 
 
b3a3223
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
targetScope = 'subscription'

@minLength(1)
@maxLength(64)
@description('Name which is used to generate a short unique hash for each resource')
param name string

@minLength(1)
@description('Primary location for all resources')
param location string

@description('Entra admin role name')
param postgresEntraAdministratorName string

@description('Entra admin role object ID (in Entra)')
param postgresEntraAdministratorObjectId string

@description('Entra admin user type')
@allowed([
  'User'
  'Group'
  'ServicePrincipal'
])
param postgresEntraAdministratorType string = 'User'

@description('Id of the user or app to assign application roles')
param principalId string = ''

@secure()
@description('Django SECRET_KEY for cryptographic signing')
param djangoSecretKey string

@description('Running on GitHub Actions?')
param runningOnGh bool = false

// Necessary for post-provision script, can be disabled after
@description('Allow all IPs to connect to the PostgreSQL server')
param postgresAllowAllIPs bool = true

var resourceToken = toLower(uniqueString(subscription().id, name, location))
var tags = { 'azd-env-name': name }

resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: '${name}-rg'
  location: location
  tags: tags
}

var prefix = '${name}-${resourceToken}'

var postgresServerName = '${prefix}-postgresql'
var postgresDatabaseName = 'postgres'

module postgresServer 'core/database/postgresql/flexibleserver.bicep' = {
  name: 'postgresql'
  scope: resourceGroup
  params: {
    name: postgresServerName
    location: location
    tags: tags
    sku: {
      name: 'Standard_B1ms'
      tier: 'Burstable'
    }
    storage: {
      storageSizeGB: 32
    }
    version: '16'
    authType: 'EntraOnly'
    entraAdministratorName: postgresEntraAdministratorName
    entraAdministratorObjectId: postgresEntraAdministratorObjectId
    entraAdministratorType: postgresEntraAdministratorType
    allowAzureIPsFirewall: true
    allowAllIPsFirewall: postgresAllowAllIPs
  }
}

var webAppName = '${prefix}-app-service'
module web 'core/host/appservice.bicep' = {
  name: 'appservice'
  scope: resourceGroup
  params: {
    name: webAppName
    location: location
    tags: union(tags, { 'azd-service-name': 'web' })
    appServicePlanId: appServicePlan.outputs.id
    runtimeName: 'python'
    runtimeVersion: '3.11'
    scmDoBuildDuringDeployment: true
    ftpsState: 'Disabled'
    managedIdentity: true
    appCommandLine: 'python manage.py migrate && gunicorn --workers 2 --threads 4 --timeout 60 --access-logfile \'-\' --error-logfile \'-\' --bind=0.0.0.0:8000 --chdir=/home/site/wwwroot quizsite.wsgi'
    appSettings: {
      ADMIN_URL: 'admin${uniqueString(appServicePlan.outputs.id)}'
      DBENGINE: 'django.db.backends.postgresql'
      DBHOST: postgresServer.outputs.POSTGRES_DOMAIN_NAME
      DBNAME: postgresDatabaseName
      DBUSER: webAppName
      DBSSL: 'require'
      STATIC_BACKEND: 'whitenoise.storage.CompressedManifestStaticFilesStorage'
      SECRET_KEY: '@Microsoft.KeyVault(VaultName=${keyVault.outputs.name};SecretName=djangoSecretKey)'
    }
  }
}

module appServicePlan 'core/host/appserviceplan.bicep' = {
  name: 'serviceplan'
  scope: resourceGroup
  params: {
    name: '${prefix}-serviceplan'
    location: location
    tags: tags
    sku: {
      name: 'B1'
    }
    reserved: true
  }
}

module webKeyVaultAccess 'core/security/keyvault-access.bicep' = {
  name: 'web-keyvault-access'
  scope: resourceGroup
  params: {
    keyVaultName: keyVault.outputs.name
    principalId: web.outputs.identityPrincipalId
  }
}

// Store secrets in a keyvault
module keyVault './core/security/keyvault.bicep' = {
  name: 'keyvault'
  scope: resourceGroup
  params: {
    name: '${take(replace(prefix, '-', ''), 17)}-vault'
    location: location
    tags: tags
  }
}

module userKeyVaultAccess 'core/security/role.bicep' = {
  name: 'user-keyvault-access'
  scope: resourceGroup
  params: {
    principalId: principalId
    principalType: runningOnGh ? 'ServicePrincipal' : 'User'
    roleDefinitionId: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
  }
}

module backendKeyVaultAccess 'core/security/role.bicep' = {
  name: 'backend-keyvault-access'
  scope: resourceGroup
  params: {
    principalId: web.outputs.identityPrincipalId
    principalType: 'ServicePrincipal'
    roleDefinitionId: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
  }
}

var secrets = [
  {
    name: 'djangoSecretKey'
    value: djangoSecretKey
  }
]

@batchSize(1)
module keyVaultSecrets './core/security/keyvault-secret.bicep' = [
  for secret in secrets: {
    name: 'keyvault-secret-${secret.name}'
    scope: resourceGroup
    params: {
      keyVaultName: keyVault.outputs.name
      name: secret.name
      secretValue: secret.value
    }
  }
]

module logAnalyticsWorkspace 'core/monitor/loganalytics.bicep' = {
  name: 'loganalytics'
  scope: resourceGroup
  params: {
    name: '${prefix}-loganalytics'
    location: location
    tags: tags
  }
}

output WEB_APP_NAME string = webAppName
output WEB_URI string = 'https://${web.outputs.uri}'
output SERVICE_WEB_IDENTITY_NAME string = webAppName

output AZURE_LOCATION string = location
output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name

output POSTGRES_HOST string = postgresServer.outputs.POSTGRES_DOMAIN_NAME
output POSTGRES_USERNAME string = postgresEntraAdministratorName
output POSTGRES_ALLOW_ALL_IPS bool = postgresAllowAllIPs