Pamela Fox commited on
Commit
9595e1d
·
1 Parent(s): f72f77b

Port to core (appservice/vnet/pg changes made)

Browse files
Files changed (31) hide show
  1. infra/core/database/cosmos/cosmos-account.bicep +48 -0
  2. infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep +22 -0
  3. infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep +46 -0
  4. infra/core/database/cosmos/sql/cosmos-sql-account.bicep +21 -0
  5. infra/core/database/cosmos/sql/cosmos-sql-db.bicep +73 -0
  6. infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep +18 -0
  7. infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep +29 -0
  8. infra/core/database/postgresql/flexibleserver.bicep +51 -0
  9. infra/core/database/sqlserver/sqlserver.bicep +129 -0
  10. infra/core/gateway/apim-api-policy.xml +92 -0
  11. infra/core/gateway/apim.bicep +61 -0
  12. infra/core/host/appservice.bicep +111 -0
  13. infra/core/host/appserviceplan.bicep +20 -0
  14. infra/core/host/container-app.bicep +77 -0
  15. infra/core/host/container-apps-environment.bicep +26 -0
  16. infra/core/host/container-apps.bicep +30 -0
  17. infra/core/host/container-registry.bicep +36 -0
  18. infra/core/host/functions.bicep +82 -0
  19. infra/core/host/staticwebapp.bicep +21 -0
  20. infra/core/monitor/applicationinsights-dashboard.bicep +1235 -0
  21. infra/core/monitor/applicationinsights.bicep +30 -0
  22. infra/core/monitor/loganalytics.bicep +21 -0
  23. infra/core/monitor/monitoring.bicep +31 -0
  24. infra/core/security/dnszone.bicep +23 -0
  25. infra/core/security/keyvault-access.bicep +21 -0
  26. infra/core/security/keyvault-secret.bicep +30 -0
  27. infra/core/security/keyvault.bicep +25 -0
  28. infra/core/security/virtualnetwork.bicep +83 -0
  29. infra/core/storage/storage-account.bicep +38 -0
  30. infra/main.bicep +88 -5
  31. infra/resources.bicep +0 -222
infra/core/database/cosmos/cosmos-account.bicep ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING'
6
+ param keyVaultName string
7
+
8
+ @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ])
9
+ param kind string
10
+
11
+ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = {
12
+ name: name
13
+ kind: kind
14
+ location: location
15
+ tags: tags
16
+ properties: {
17
+ consistencyPolicy: { defaultConsistencyLevel: 'Session' }
18
+ locations: [
19
+ {
20
+ locationName: location
21
+ failoverPriority: 0
22
+ isZoneRedundant: false
23
+ }
24
+ ]
25
+ databaseAccountOfferType: 'Standard'
26
+ enableAutomaticFailover: false
27
+ enableMultipleWriteLocations: false
28
+ apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.0' } : {}
29
+ capabilities: [ { name: 'EnableServerless' } ]
30
+ }
31
+ }
32
+
33
+ resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
34
+ parent: keyVault
35
+ name: connectionStringKey
36
+ properties: {
37
+ value: cosmos.listConnectionStrings().connectionStrings[0].connectionString
38
+ }
39
+ }
40
+
41
+ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
42
+ name: keyVaultName
43
+ }
44
+
45
+ output connectionStringKey string = connectionStringKey
46
+ output endpoint string = cosmos.properties.documentEndpoint
47
+ output id string = cosmos.id
48
+ output name string = cosmos.name
infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param keyVaultName string
6
+ param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING'
7
+
8
+ module cosmos '../../cosmos/cosmos-account.bicep' = {
9
+ name: 'cosmos-account'
10
+ params: {
11
+ name: name
12
+ location: location
13
+ connectionStringKey: connectionStringKey
14
+ keyVaultName: keyVaultName
15
+ kind: 'MongoDB'
16
+ tags: tags
17
+ }
18
+ }
19
+
20
+ output connectionStringKey string = cosmos.outputs.connectionStringKey
21
+ output endpoint string = cosmos.outputs.endpoint
22
+ output id string = cosmos.outputs.id
infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param accountName string
2
+ param databaseName string
3
+ param location string = resourceGroup().location
4
+ param tags object = {}
5
+
6
+ param collections array = []
7
+ param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING'
8
+ param keyVaultName string
9
+
10
+ module cosmos 'cosmos-mongo-account.bicep' = {
11
+ name: 'cosmos-mongo-account'
12
+ params: {
13
+ name: accountName
14
+ location: location
15
+ keyVaultName: keyVaultName
16
+ tags: tags
17
+ connectionStringKey: connectionStringKey
18
+ }
19
+ }
20
+
21
+ resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = {
22
+ name: '${accountName}/${databaseName}'
23
+ tags: tags
24
+ properties: {
25
+ resource: { id: databaseName }
26
+ }
27
+
28
+ resource list 'collections' = [for collection in collections: {
29
+ name: collection.name
30
+ properties: {
31
+ resource: {
32
+ id: collection.id
33
+ shardKey: { _id: collection.shardKey }
34
+ indexes: [ { key: { keys: [ collection.indexKey ] } } ]
35
+ }
36
+ }
37
+ }]
38
+
39
+ dependsOn: [
40
+ cosmos
41
+ ]
42
+ }
43
+
44
+ output connectionStringKey string = connectionStringKey
45
+ output databaseName string = databaseName
46
+ output endpoint string = cosmos.outputs.endpoint
infra/core/database/cosmos/sql/cosmos-sql-account.bicep ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param keyVaultName string
6
+
7
+ module cosmos '../../cosmos/cosmos-account.bicep' = {
8
+ name: 'cosmos-account'
9
+ params: {
10
+ name: name
11
+ location: location
12
+ tags: tags
13
+ keyVaultName: keyVaultName
14
+ kind: 'GlobalDocumentDB'
15
+ }
16
+ }
17
+
18
+ output connectionStringKey string = cosmos.outputs.connectionStringKey
19
+ output endpoint string = cosmos.outputs.endpoint
20
+ output id string = cosmos.outputs.id
21
+ output name string = cosmos.outputs.name
infra/core/database/cosmos/sql/cosmos-sql-db.bicep ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param accountName string
2
+ param databaseName string
3
+ param location string = resourceGroup().location
4
+ param tags object = {}
5
+
6
+ param containers array = []
7
+ param keyVaultName string
8
+ param principalIds array = []
9
+
10
+ module cosmos 'cosmos-sql-account.bicep' = {
11
+ name: 'cosmos-sql-account'
12
+ params: {
13
+ name: accountName
14
+ location: location
15
+ tags: tags
16
+ keyVaultName: keyVaultName
17
+ }
18
+ }
19
+
20
+ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = {
21
+ name: '${accountName}/${databaseName}'
22
+ properties: {
23
+ resource: { id: databaseName }
24
+ }
25
+
26
+ resource list 'containers' = [for container in containers: {
27
+ name: container.name
28
+ properties: {
29
+ resource: {
30
+ id: container.id
31
+ partitionKey: { paths: [ container.partitionKey ] }
32
+ }
33
+ options: {}
34
+ }
35
+ }]
36
+
37
+ dependsOn: [
38
+ cosmos
39
+ ]
40
+ }
41
+
42
+ module roleDefintion 'cosmos-sql-role-def.bicep' = {
43
+ name: 'cosmos-sql-role-definition'
44
+ params: {
45
+ accountName: accountName
46
+ }
47
+ dependsOn: [
48
+ cosmos
49
+ database
50
+ ]
51
+ }
52
+
53
+ // We need batchSize(1) here because sql role assignments have to be done sequentially
54
+ @batchSize(1)
55
+ module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) {
56
+ name: 'cosmos-sql-user-role-${uniqueString(principalId)}'
57
+ params: {
58
+ accountName: accountName
59
+ roleDefinitionId: roleDefintion.outputs.id
60
+ principalId: principalId
61
+ }
62
+ dependsOn: [
63
+ cosmos
64
+ database
65
+ ]
66
+ }]
67
+
68
+ output accountId string = cosmos.outputs.id
69
+ output accountName string = cosmos.outputs.name
70
+ output connectionStringKey string = cosmos.outputs.connectionStringKey
71
+ output databaseName string = databaseName
72
+ output endpoint string = cosmos.outputs.endpoint
73
+ output roleDefinitionId string = roleDefintion.outputs.id
infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param accountName string
2
+
3
+ param roleDefinitionId string
4
+ param principalId string = ''
5
+
6
+ resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = {
7
+ parent: cosmos
8
+ name: guid(roleDefinitionId, principalId, cosmos.id)
9
+ properties: {
10
+ principalId: principalId
11
+ roleDefinitionId: roleDefinitionId
12
+ scope: cosmos.id
13
+ }
14
+ }
15
+
16
+ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = {
17
+ name: accountName
18
+ }
infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param accountName string
2
+
3
+ resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = {
4
+ parent: cosmos
5
+ name: guid(cosmos.id, accountName, 'sql-role')
6
+ properties: {
7
+ assignableScopes: [
8
+ cosmos.id
9
+ ]
10
+ permissions: [
11
+ {
12
+ dataActions: [
13
+ 'Microsoft.DocumentDB/databaseAccounts/readMetadata'
14
+ 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
15
+ 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*'
16
+ ]
17
+ notDataActions: []
18
+ }
19
+ ]
20
+ roleName: 'Reader Writer'
21
+ type: 'CustomRole'
22
+ }
23
+ }
24
+
25
+ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = {
26
+ name: accountName
27
+ }
28
+
29
+ output id string = roleDefinition.id
infra/core/database/postgresql/flexibleserver.bicep ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param sku object
6
+ param storage object
7
+ param delegatedSubnetResourceId string = ''
8
+ param privateDnsZoneArmResourceId string = ''
9
+ param privateDnsZoneLink object = {}
10
+
11
+ param databaseName string
12
+ param administratorLogin string
13
+ @secure()
14
+ param administratorLoginPassword string
15
+
16
+ // PostgreSQL version
17
+ @allowed(['11', '12', '13', '14', '15'])
18
+ param version string
19
+
20
+ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = {
21
+ location: location
22
+ tags: tags
23
+ name: name
24
+ sku: sku
25
+ properties: {
26
+ version: version
27
+ administratorLogin: administratorLogin
28
+ administratorLoginPassword: administratorLoginPassword
29
+ storage: storage
30
+ network: union(
31
+ !empty(delegatedSubnetResourceId) ? { delegatedSubnetResourceId: delegatedSubnetResourceId } : {},
32
+ !empty(privateDnsZoneArmResourceId) ? {privateDnsZoneArmResourceId: privateDnsZoneArmResourceId } : {})
33
+ highAvailability: {
34
+ mode: 'Disabled'
35
+ }
36
+ }
37
+
38
+ resource database 'databases' = {
39
+ name: databaseName
40
+ }
41
+
42
+ resource firewall 'firewallRules' = {
43
+ name: 'AllowAllWindowsAzureIps'
44
+ properties: {
45
+ startIpAddress: '0.0.0.0'
46
+ endIpAddress: '0.0.0.0'
47
+ }
48
+ }
49
+
50
+ dependsOn: empty(privateDnsZoneLink) ? [] : [privateDnsZoneLink]
51
+ }
infra/core/database/sqlserver/sqlserver.bicep ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param appUser string = 'appUser'
6
+ param databaseName string
7
+ param keyVaultName string
8
+ param sqlAdmin string = 'sqlAdmin'
9
+ param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING'
10
+
11
+ @secure()
12
+ param sqlAdminPassword string
13
+ @secure()
14
+ param appUserPassword string
15
+
16
+ resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
17
+ name: name
18
+ location: location
19
+ tags: tags
20
+ properties: {
21
+ version: '12.0'
22
+ minimalTlsVersion: '1.2'
23
+ publicNetworkAccess: 'Enabled'
24
+ administratorLogin: sqlAdmin
25
+ administratorLoginPassword: sqlAdminPassword
26
+ }
27
+
28
+ resource database 'databases' = {
29
+ name: databaseName
30
+ location: location
31
+ }
32
+
33
+ resource firewall 'firewallRules' = {
34
+ name: 'Azure Services'
35
+ properties: {
36
+ // Allow all clients
37
+ // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only".
38
+ // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes.
39
+ startIpAddress: '0.0.0.1'
40
+ endIpAddress: '255.255.255.254'
41
+ }
42
+ }
43
+ }
44
+
45
+ resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
46
+ name: '${name}-deployment-script'
47
+ location: location
48
+ kind: 'AzureCLI'
49
+ properties: {
50
+ azCliVersion: '2.37.0'
51
+ retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running
52
+ timeout: 'PT5M' // Five minutes
53
+ cleanupPreference: 'OnSuccess'
54
+ environmentVariables: [
55
+ {
56
+ name: 'APPUSERNAME'
57
+ value: appUser
58
+ }
59
+ {
60
+ name: 'APPUSERPASSWORD'
61
+ secureValue: appUserPassword
62
+ }
63
+ {
64
+ name: 'DBNAME'
65
+ value: databaseName
66
+ }
67
+ {
68
+ name: 'DBSERVER'
69
+ value: sqlServer.properties.fullyQualifiedDomainName
70
+ }
71
+ {
72
+ name: 'SQLCMDPASSWORD'
73
+ secureValue: sqlAdminPassword
74
+ }
75
+ {
76
+ name: 'SQLADMIN'
77
+ value: sqlAdmin
78
+ }
79
+ ]
80
+
81
+ scriptContent: '''
82
+ wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2
83
+ tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C .
84
+
85
+ cat <<SCRIPT_END > ./initDb.sql
86
+ drop user ${APPUSERNAME}
87
+ go
88
+ create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}'
89
+ go
90
+ alter role db_owner add member ${APPUSERNAME}
91
+ go
92
+ SCRIPT_END
93
+
94
+ ./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql
95
+ '''
96
+ }
97
+ }
98
+
99
+ resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
100
+ parent: keyVault
101
+ name: 'sqlAdminPassword'
102
+ properties: {
103
+ value: sqlAdminPassword
104
+ }
105
+ }
106
+
107
+ resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
108
+ parent: keyVault
109
+ name: 'appUserPassword'
110
+ properties: {
111
+ value: appUserPassword
112
+ }
113
+ }
114
+
115
+ resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
116
+ parent: keyVault
117
+ name: connectionStringKey
118
+ properties: {
119
+ value: '${connectionString}; Password=${appUserPassword}'
120
+ }
121
+ }
122
+
123
+ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
124
+ name: keyVaultName
125
+ }
126
+
127
+ var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}'
128
+ output connectionStringKey string = connectionStringKey
129
+ output databaseName string = sqlServer::database.name
infra/core/gateway/apim-api-policy.xml ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- Policy configuration for the API. Explore other sample policies at https://learn.microsoft.com/en-us/azure/api-management/policies/ -->
2
+ <policies>
3
+ <inbound>
4
+ <base />
5
+ <!-- This policy is needed to handle preflight requests using the OPTIONS method. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-cross-domain-policies -->
6
+ <cors allow-credentials="false">
7
+ <allowed-origins>
8
+ <origin>{0}</origin>
9
+ </allowed-origins>
10
+ <allowed-methods>
11
+ <method>PUT</method>
12
+ <method>GET</method>
13
+ <method>POST</method>
14
+ <method>DELETE</method>
15
+ <method>PATCH</method>
16
+ </allowed-methods>
17
+ <allowed-headers>
18
+ <header>*</header>
19
+ </allowed-headers>
20
+ <expose-headers>
21
+ <header>*</header>
22
+ </expose-headers>
23
+ </cors>
24
+ <!-- Optional policy to validate the request content. Learn more at https://learn.microsoft.com/en-us/azure/api-management/validation-policies#validate-content -->
25
+ <validate-content unspecified-content-type-action="ignore" max-size="1024" size-exceeded-action="detect" errors-variable-name="requestBodyValidation">
26
+ <content type="application/json" validate-as="json" action="detect" />
27
+ </validate-content>
28
+ <!-- Optional policy to send custom trace telemetry to Application Insights. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#Trace -->
29
+ <trace source="@(context.Api.Name)" severity="verbose">
30
+ <message>Call to the @(context.Api.Name)</message>
31
+ <metadata name="User-Agent" value="@(context.Request.Headers.GetValueOrDefault("User-Agent",""))" />
32
+ <metadata name="Operation Method" value="@(context.Request.Method)" />
33
+ <metadata name="Host" value="@(context.Request.Url.Host)" />
34
+ <metadata name="Path" value="@(context.Request.Url.Path)" />
35
+ </trace>
36
+ </inbound>
37
+ <backend>
38
+ <limit-concurrency key="@(context.Request.IpAddress)" max-count="3">
39
+ <forward-request timeout="120" />
40
+ </limit-concurrency>
41
+ </backend>
42
+ <outbound>
43
+ <base />
44
+ <!-- Optional policy to validate the response headers. Learn more at https://learn.microsoft.com/en-us/azure/api-management/validation-policies#validate-headers -->
45
+ <validate-headers specified-header-action="ignore" unspecified-header-action="ignore" errors-variable-name="responseHeadersValidation" />
46
+ <!-- Optional policy to to send custom metrics to Application Insights. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#emit-metrics -->
47
+ <choose>
48
+ <when condition="@(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300)">
49
+ <emit-metric name="Successful requests" value="1" namespace="apim-metrics">
50
+ <dimension name="API" value="@(context.Api.Name)" />
51
+ <dimension name="Client IP" value="@(context.Request.IpAddress)" />
52
+ <dimension name="Status Code" value="@((String)context.Response.StatusCode.ToString())" />
53
+ <dimension name="Status Reason" value="@(context.Response.StatusReason)" />
54
+ </emit-metric>
55
+ </when>
56
+ <when condition="@(context.Response.StatusCode >= 400 && context.Response.StatusCode < 600)">
57
+ <emit-metric name="Failed requests" value="1" namespace="apim-metrics">
58
+ <dimension name="API" value="@(context.Api.Name)" />
59
+ <dimension name="Client IP" value="@(context.Request.IpAddress)" />
60
+ <dimension name="Status Code" value="@(context.Response.StatusCode.ToString())" />
61
+ <dimension name="Status Reason" value="@(context.Response.StatusReason)" />
62
+ <dimension name="Error Source" value="backend" />
63
+ </emit-metric>
64
+ </when>
65
+ </choose>
66
+ </outbound>
67
+ <on-error>
68
+ <base />
69
+ <!-- Optional policy to handle errors. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-error-handling-policies -->
70
+ <trace source="@(context.Api.Name)" severity="error">
71
+ <message>Failed to process the @(context.Api.Name)</message>
72
+ <metadata name="User-Agent" value="@(context.Request.Headers.GetValueOrDefault("User-Agent",""))" />
73
+ <metadata name="Operation Method" value="@(context.Request.Method)" />
74
+ <metadata name="Host" value="@(context.Request.Url.Host)" />
75
+ <metadata name="Path" value="@(context.Request.Url.Path)" />
76
+ <metadata name="Error Reason" value="@(context.LastError.Reason)" />
77
+ <metadata name="Error Message" value="@(context.LastError.Message)" />
78
+ </trace>
79
+ <emit-metric name="Failed requests" value="1" namespace="apim-metrics">
80
+ <dimension name="API" value="@(context.Api.Name)" />
81
+ <dimension name="Client IP" value="@(context.Request.IpAddress)" />
82
+ <dimension name="Status Code" value="500" />
83
+ <dimension name="Status Reason" value="@(context.LastError.Reason)" />
84
+ <dimension name="Error Source" value="gateway" />
85
+ </emit-metric>
86
+ <!-- Optional policy to hide error details and provide a custom generic message. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#ReturnResponse -->
87
+ <return-response>
88
+ <set-status code="500" reason="Internal Server Error" />
89
+ <set-body>We're Sorry. An unexpected error has occurred. If this continues please contact Tech Support.</set-body>
90
+ </return-response>
91
+ </on-error>
92
+ </policies>
infra/core/gateway/apim.bicep ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ @description('The email address of the owner of the service')
6
+ @minLength(1)
7
+ param publisherEmail string = '[email protected]'
8
+
9
+ @description('The name of the owner of the service')
10
+ @minLength(1)
11
+ param publisherName string = 'n/a'
12
+
13
+ @description('The pricing tier of this API Management service')
14
+ @allowed([
15
+ 'Consumption'
16
+ 'Developer'
17
+ 'Standard'
18
+ 'Premium'
19
+ ])
20
+ param sku string = 'Consumption'
21
+
22
+ @description('The instance size of this API Management service.')
23
+ @allowed([ 0, 1, 2 ])
24
+ param skuCount int = 0
25
+
26
+ @description('Azure Application Insights Name')
27
+ param applicationInsightsName string
28
+
29
+ resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = {
30
+ name: name
31
+ location: location
32
+ tags: union(tags, { 'azd-service-name': name })
33
+ sku: {
34
+ name: sku
35
+ capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount)
36
+ }
37
+ properties: {
38
+ publisherEmail: publisherEmail
39
+ publisherName: publisherName
40
+ }
41
+ }
42
+
43
+ resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) {
44
+ name: 'app-insights-logger'
45
+ parent: apimService
46
+ properties: {
47
+ credentials: {
48
+ instrumentationKey: applicationInsights.properties.InstrumentationKey
49
+ }
50
+ description: 'Logger to Azure Application Insights'
51
+ isBuffered: false
52
+ loggerType: 'applicationInsights'
53
+ resourceId: applicationInsights.id
54
+ }
55
+ }
56
+
57
+ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) {
58
+ name: applicationInsightsName
59
+ }
60
+
61
+ output apimServiceName string = apimService.name
infra/core/host/appservice.bicep ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ // Reference Properties
6
+ param applicationInsightsName string = ''
7
+ param appServicePlanId string
8
+ param keyVaultName string = ''
9
+ param managedIdentity bool = !empty(keyVaultName)
10
+
11
+ // Runtime Properties
12
+ @allowed([
13
+ 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom'
14
+ ])
15
+ param runtimeName string
16
+ param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}'
17
+ param runtimeVersion string
18
+
19
+ // Microsoft.Web/sites Properties
20
+ param kind string = 'app,linux'
21
+
22
+ // Microsoft.Web/sites/config
23
+ param allowedOrigins array = []
24
+ param alwaysOn bool = true
25
+ param appCommandLine string = ''
26
+ param appSettings object = {}
27
+ param clientAffinityEnabled bool = false
28
+ param enableOryxBuild bool = contains(kind, 'linux')
29
+ param functionAppScaleLimit int = -1
30
+ param linuxFxVersion string = runtimeNameAndVersion
31
+ param minimumElasticInstanceCount int = -1
32
+ param numberOfWorkers int = -1
33
+ param scmDoBuildDuringDeployment bool = false
34
+ param use32BitWorkerProcess bool = false
35
+ param ftpsState string = 'FtpsOnly'
36
+
37
+ // Microsoft.Web/sites/networkConfig
38
+ param subnetResourceId string = ''
39
+ param virtualNetwork object
40
+
41
+ resource appService 'Microsoft.Web/sites@2022-03-01' = {
42
+ name: name
43
+ location: location
44
+ tags: tags
45
+ kind: kind
46
+ properties: {
47
+ serverFarmId: appServicePlanId
48
+ siteConfig: {
49
+ linuxFxVersion: linuxFxVersion
50
+ alwaysOn: alwaysOn
51
+ ftpsState: ftpsState
52
+ appCommandLine: appCommandLine
53
+ numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null
54
+ minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null
55
+ use32BitWorkerProcess: use32BitWorkerProcess
56
+ functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null
57
+ cors: {
58
+ allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins)
59
+ }
60
+ }
61
+ clientAffinityEnabled: clientAffinityEnabled
62
+ httpsOnly: true
63
+ }
64
+
65
+ identity: { type: managedIdentity ? 'SystemAssigned' : 'None' }
66
+
67
+ resource configAppSettings 'config' = {
68
+ name: 'appsettings'
69
+ properties: union(appSettings,
70
+ {
71
+ SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment)
72
+ ENABLE_ORYX_BUILD: string(enableOryxBuild)
73
+ },
74
+ !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {},
75
+ !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {})
76
+ }
77
+
78
+ resource configLogs 'config' = {
79
+ name: 'logs'
80
+ properties: {
81
+ applicationLogs: { fileSystem: { level: 'Verbose' } }
82
+ detailedErrorMessages: { enabled: true }
83
+ failedRequestsTracing: { enabled: true }
84
+ httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } }
85
+ }
86
+ dependsOn: [
87
+ configAppSettings
88
+ ]
89
+ }
90
+
91
+ resource webappVnetConfig 'networkConfig' = if (!(empty(virtualNetwork))) {
92
+ name: 'virtualNetwork'
93
+ properties: {
94
+ subnetResourceId: subnetResourceId
95
+ }
96
+ }
97
+
98
+ dependsOn: empty(virtualNetwork) ? [] : [virtualNetwork]
99
+ }
100
+
101
+ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) {
102
+ name: keyVaultName
103
+ }
104
+
105
+ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) {
106
+ name: applicationInsightsName
107
+ }
108
+
109
+ output identityPrincipalId string = managedIdentity ? appService.identity.principalId : ''
110
+ output name string = appService.name
111
+ output uri string = 'https://${appService.properties.defaultHostName}'
infra/core/host/appserviceplan.bicep ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param kind string = ''
6
+ param reserved bool = true
7
+ param sku object
8
+
9
+ resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
10
+ name: name
11
+ location: location
12
+ tags: tags
13
+ sku: sku
14
+ kind: kind
15
+ properties: {
16
+ reserved: reserved
17
+ }
18
+ }
19
+
20
+ output id string = appServicePlan.id
infra/core/host/container-app.bicep ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param containerAppsEnvironmentName string = ''
6
+ param containerName string = 'main'
7
+ param containerRegistryName string = ''
8
+ param env array = []
9
+ param external bool = true
10
+ param imageName string
11
+ param keyVaultName string = ''
12
+ param managedIdentity bool = !empty(keyVaultName)
13
+ param targetPort int = 80
14
+
15
+ @description('CPU cores allocated to a single container instance, e.g. 0.5')
16
+ param containerCpuCoreCount string = '0.5'
17
+
18
+ @description('Memory allocated to a single container instance, e.g. 1Gi')
19
+ param containerMemory string = '1.0Gi'
20
+
21
+ resource app 'Microsoft.App/containerApps@2022-03-01' = {
22
+ name: name
23
+ location: location
24
+ tags: tags
25
+ identity: { type: managedIdentity ? 'SystemAssigned' : 'None' }
26
+ properties: {
27
+ managedEnvironmentId: containerAppsEnvironment.id
28
+ configuration: {
29
+ activeRevisionsMode: 'single'
30
+ ingress: {
31
+ external: external
32
+ targetPort: targetPort
33
+ transport: 'auto'
34
+ }
35
+ secrets: [
36
+ {
37
+ name: 'registry-password'
38
+ value: containerRegistry.listCredentials().passwords[0].value
39
+ }
40
+ ]
41
+ registries: [
42
+ {
43
+ server: '${containerRegistry.name}.azurecr.io'
44
+ username: containerRegistry.name
45
+ passwordSecretRef: 'registry-password'
46
+ }
47
+ ]
48
+ }
49
+ template: {
50
+ containers: [
51
+ {
52
+ image: imageName
53
+ name: containerName
54
+ env: env
55
+ resources: {
56
+ cpu: json(containerCpuCoreCount)
57
+ memory: containerMemory
58
+ }
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ }
64
+
65
+ resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
66
+ name: containerAppsEnvironmentName
67
+ }
68
+
69
+ // 2022-02-01-preview needed for anonymousPullEnabled
70
+ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = {
71
+ name: containerRegistryName
72
+ }
73
+
74
+ output identityPrincipalId string = managedIdentity ? app.identity.principalId : ''
75
+ output imageName string = imageName
76
+ output name string = app.name
77
+ output uri string = 'https://${app.properties.configuration.ingress.fqdn}'
infra/core/host/container-apps-environment.bicep ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param logAnalyticsWorkspaceName string
6
+
7
+ resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = {
8
+ name: name
9
+ location: location
10
+ tags: tags
11
+ properties: {
12
+ appLogsConfiguration: {
13
+ destination: 'log-analytics'
14
+ logAnalyticsConfiguration: {
15
+ customerId: logAnalyticsWorkspace.properties.customerId
16
+ sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
17
+ }
18
+ }
19
+ }
20
+ }
21
+
22
+ resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = {
23
+ name: logAnalyticsWorkspaceName
24
+ }
25
+
26
+ output name string = containerAppsEnvironment.name
infra/core/host/container-apps.bicep ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param containerAppsEnvironmentName string = ''
6
+ param containerRegistryName string = ''
7
+ param logAnalyticsWorkspaceName string = ''
8
+
9
+ module containerAppsEnvironment 'container-apps-environment.bicep' = {
10
+ name: '${name}-container-apps-environment'
11
+ params: {
12
+ name: containerAppsEnvironmentName
13
+ location: location
14
+ tags: tags
15
+ logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
16
+ }
17
+ }
18
+
19
+ module containerRegistry 'container-registry.bicep' = {
20
+ name: '${name}-container-registry'
21
+ params: {
22
+ name: containerRegistryName
23
+ location: location
24
+ tags: tags
25
+ }
26
+ }
27
+
28
+ output environmentName string = containerAppsEnvironment.outputs.name
29
+ output registryLoginServer string = containerRegistry.outputs.loginServer
30
+ output registryName string = containerRegistry.outputs.name
infra/core/host/container-registry.bicep ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param adminUserEnabled bool = true
6
+ param anonymousPullEnabled bool = false
7
+ param dataEndpointEnabled bool = false
8
+ param encryption object = {
9
+ status: 'disabled'
10
+ }
11
+ param networkRuleBypassOptions string = 'AzureServices'
12
+ param publicNetworkAccess string = 'Enabled'
13
+ param sku object = {
14
+ name: 'Basic'
15
+ }
16
+ param zoneRedundancy string = 'Disabled'
17
+
18
+ // 2022-02-01-preview needed for anonymousPullEnabled
19
+ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = {
20
+ name: name
21
+ location: location
22
+ tags: tags
23
+ sku: sku
24
+ properties: {
25
+ adminUserEnabled: adminUserEnabled
26
+ anonymousPullEnabled: anonymousPullEnabled
27
+ dataEndpointEnabled: dataEndpointEnabled
28
+ encryption: encryption
29
+ networkRuleBypassOptions: networkRuleBypassOptions
30
+ publicNetworkAccess: publicNetworkAccess
31
+ zoneRedundancy: zoneRedundancy
32
+ }
33
+ }
34
+
35
+ output loginServer string = containerRegistry.properties.loginServer
36
+ output name string = containerRegistry.name
infra/core/host/functions.bicep ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ // Reference Properties
6
+ param applicationInsightsName string = ''
7
+ param appServicePlanId string
8
+ param keyVaultName string = ''
9
+ param managedIdentity bool = !empty(keyVaultName)
10
+ param storageAccountName string
11
+
12
+ // Runtime Properties
13
+ @allowed([
14
+ 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom'
15
+ ])
16
+ param runtimeName string
17
+ param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}'
18
+ param runtimeVersion string
19
+
20
+ // Function Settings
21
+ @allowed([
22
+ '~4', '~3', '~2', '~1'
23
+ ])
24
+ param extensionVersion string = '~4'
25
+
26
+ // Microsoft.Web/sites Properties
27
+ param kind string = 'functionapp,linux'
28
+
29
+ // Microsoft.Web/sites/config
30
+ param allowedOrigins array = []
31
+ param alwaysOn bool = true
32
+ param appCommandLine string = ''
33
+ param appSettings object = {}
34
+ param clientAffinityEnabled bool = false
35
+ param enableOryxBuild bool = contains(kind, 'linux')
36
+ param functionAppScaleLimit int = -1
37
+ param linuxFxVersion string = runtimeNameAndVersion
38
+ param minimumElasticInstanceCount int = -1
39
+ param numberOfWorkers int = -1
40
+ param scmDoBuildDuringDeployment bool = true
41
+ param use32BitWorkerProcess bool = false
42
+
43
+ module functions 'appservice.bicep' = {
44
+ name: '${name}-functions'
45
+ params: {
46
+ name: name
47
+ location: location
48
+ tags: tags
49
+ allowedOrigins: allowedOrigins
50
+ alwaysOn: alwaysOn
51
+ appCommandLine: appCommandLine
52
+ applicationInsightsName: applicationInsightsName
53
+ appServicePlanId: appServicePlanId
54
+ appSettings: union(appSettings, {
55
+ AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
56
+ FUNCTIONS_EXTENSION_VERSION: extensionVersion
57
+ FUNCTIONS_WORKER_RUNTIME: runtimeName
58
+ })
59
+ clientAffinityEnabled: clientAffinityEnabled
60
+ enableOryxBuild: enableOryxBuild
61
+ functionAppScaleLimit: functionAppScaleLimit
62
+ keyVaultName: keyVaultName
63
+ kind: kind
64
+ linuxFxVersion: linuxFxVersion
65
+ managedIdentity: managedIdentity
66
+ minimumElasticInstanceCount: minimumElasticInstanceCount
67
+ numberOfWorkers: numberOfWorkers
68
+ runtimeName: runtimeName
69
+ runtimeVersion: runtimeVersion
70
+ runtimeNameAndVersion: runtimeNameAndVersion
71
+ scmDoBuildDuringDeployment: scmDoBuildDuringDeployment
72
+ use32BitWorkerProcess: use32BitWorkerProcess
73
+ }
74
+ }
75
+
76
+ resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = {
77
+ name: storageAccountName
78
+ }
79
+
80
+ output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : ''
81
+ output name string = functions.outputs.name
82
+ output uri string = functions.outputs.uri
infra/core/host/staticwebapp.bicep ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param sku object = {
6
+ name: 'Free'
7
+ tier: 'Free'
8
+ }
9
+
10
+ resource web 'Microsoft.Web/staticSites@2022-03-01' = {
11
+ name: name
12
+ location: location
13
+ tags: tags
14
+ sku: sku
15
+ properties: {
16
+ provider: 'Custom'
17
+ }
18
+ }
19
+
20
+ output name string = web.name
21
+ output uri string = 'https://${web.properties.defaultHostname}'
infra/core/monitor/applicationinsights-dashboard.bicep ADDED
@@ -0,0 +1,1235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param applicationInsightsName string
3
+ param location string = resourceGroup().location
4
+ param tags object = {}
5
+
6
+ // 2020-09-01-preview because that is the latest valid version
7
+ resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = {
8
+ name: name
9
+ location: location
10
+ tags: tags
11
+ properties: {
12
+ lenses: [
13
+ {
14
+ order: 0
15
+ parts: [
16
+ {
17
+ position: {
18
+ x: 0
19
+ y: 0
20
+ colSpan: 2
21
+ rowSpan: 1
22
+ }
23
+ metadata: {
24
+ inputs: [
25
+ {
26
+ name: 'id'
27
+ value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
28
+ }
29
+ {
30
+ name: 'Version'
31
+ value: '1.0'
32
+ }
33
+ ]
34
+ #disable-next-line BCP036
35
+ type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart'
36
+ asset: {
37
+ idInputName: 'id'
38
+ type: 'ApplicationInsights'
39
+ }
40
+ defaultMenuItemId: 'overview'
41
+ }
42
+ }
43
+ {
44
+ position: {
45
+ x: 2
46
+ y: 0
47
+ colSpan: 1
48
+ rowSpan: 1
49
+ }
50
+ metadata: {
51
+ inputs: [
52
+ {
53
+ name: 'ComponentId'
54
+ value: {
55
+ Name: applicationInsights.name
56
+ SubscriptionId: subscription().subscriptionId
57
+ ResourceGroup: resourceGroup().name
58
+ }
59
+ }
60
+ {
61
+ name: 'Version'
62
+ value: '1.0'
63
+ }
64
+ ]
65
+ #disable-next-line BCP036
66
+ type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart'
67
+ asset: {
68
+ idInputName: 'ComponentId'
69
+ type: 'ApplicationInsights'
70
+ }
71
+ defaultMenuItemId: 'ProactiveDetection'
72
+ }
73
+ }
74
+ {
75
+ position: {
76
+ x: 3
77
+ y: 0
78
+ colSpan: 1
79
+ rowSpan: 1
80
+ }
81
+ metadata: {
82
+ inputs: [
83
+ {
84
+ name: 'ComponentId'
85
+ value: {
86
+ Name: applicationInsights.name
87
+ SubscriptionId: subscription().subscriptionId
88
+ ResourceGroup: resourceGroup().name
89
+ }
90
+ }
91
+ {
92
+ name: 'ResourceId'
93
+ value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
94
+ }
95
+ ]
96
+ #disable-next-line BCP036
97
+ type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart'
98
+ asset: {
99
+ idInputName: 'ComponentId'
100
+ type: 'ApplicationInsights'
101
+ }
102
+ }
103
+ }
104
+ {
105
+ position: {
106
+ x: 4
107
+ y: 0
108
+ colSpan: 1
109
+ rowSpan: 1
110
+ }
111
+ metadata: {
112
+ inputs: [
113
+ {
114
+ name: 'ComponentId'
115
+ value: {
116
+ Name: applicationInsights.name
117
+ SubscriptionId: subscription().subscriptionId
118
+ ResourceGroup: resourceGroup().name
119
+ }
120
+ }
121
+ {
122
+ name: 'TimeContext'
123
+ value: {
124
+ durationMs: 86400000
125
+ endTime: null
126
+ createdTime: '2018-05-04T01:20:33.345Z'
127
+ isInitialTime: true
128
+ grain: 1
129
+ useDashboardTimeRange: false
130
+ }
131
+ }
132
+ {
133
+ name: 'Version'
134
+ value: '1.0'
135
+ }
136
+ ]
137
+ #disable-next-line BCP036
138
+ type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart'
139
+ asset: {
140
+ idInputName: 'ComponentId'
141
+ type: 'ApplicationInsights'
142
+ }
143
+ }
144
+ }
145
+ {
146
+ position: {
147
+ x: 5
148
+ y: 0
149
+ colSpan: 1
150
+ rowSpan: 1
151
+ }
152
+ metadata: {
153
+ inputs: [
154
+ {
155
+ name: 'ComponentId'
156
+ value: {
157
+ Name: applicationInsights.name
158
+ SubscriptionId: subscription().subscriptionId
159
+ ResourceGroup: resourceGroup().name
160
+ }
161
+ }
162
+ {
163
+ name: 'TimeContext'
164
+ value: {
165
+ durationMs: 86400000
166
+ endTime: null
167
+ createdTime: '2018-05-08T18:47:35.237Z'
168
+ isInitialTime: true
169
+ grain: 1
170
+ useDashboardTimeRange: false
171
+ }
172
+ }
173
+ {
174
+ name: 'ConfigurationId'
175
+ value: '78ce933e-e864-4b05-a27b-71fd55a6afad'
176
+ }
177
+ ]
178
+ #disable-next-line BCP036
179
+ type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart'
180
+ asset: {
181
+ idInputName: 'ComponentId'
182
+ type: 'ApplicationInsights'
183
+ }
184
+ }
185
+ }
186
+ {
187
+ position: {
188
+ x: 0
189
+ y: 1
190
+ colSpan: 3
191
+ rowSpan: 1
192
+ }
193
+ metadata: {
194
+ inputs: []
195
+ type: 'Extension/HubsExtension/PartType/MarkdownPart'
196
+ settings: {
197
+ content: {
198
+ settings: {
199
+ content: '# Usage'
200
+ title: ''
201
+ subtitle: ''
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+ {
208
+ position: {
209
+ x: 3
210
+ y: 1
211
+ colSpan: 1
212
+ rowSpan: 1
213
+ }
214
+ metadata: {
215
+ inputs: [
216
+ {
217
+ name: 'ComponentId'
218
+ value: {
219
+ Name: applicationInsights.name
220
+ SubscriptionId: subscription().subscriptionId
221
+ ResourceGroup: resourceGroup().name
222
+ }
223
+ }
224
+ {
225
+ name: 'TimeContext'
226
+ value: {
227
+ durationMs: 86400000
228
+ endTime: null
229
+ createdTime: '2018-05-04T01:22:35.782Z'
230
+ isInitialTime: true
231
+ grain: 1
232
+ useDashboardTimeRange: false
233
+ }
234
+ }
235
+ ]
236
+ #disable-next-line BCP036
237
+ type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart'
238
+ asset: {
239
+ idInputName: 'ComponentId'
240
+ type: 'ApplicationInsights'
241
+ }
242
+ }
243
+ }
244
+ {
245
+ position: {
246
+ x: 4
247
+ y: 1
248
+ colSpan: 3
249
+ rowSpan: 1
250
+ }
251
+ metadata: {
252
+ inputs: []
253
+ type: 'Extension/HubsExtension/PartType/MarkdownPart'
254
+ settings: {
255
+ content: {
256
+ settings: {
257
+ content: '# Reliability'
258
+ title: ''
259
+ subtitle: ''
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ {
266
+ position: {
267
+ x: 7
268
+ y: 1
269
+ colSpan: 1
270
+ rowSpan: 1
271
+ }
272
+ metadata: {
273
+ inputs: [
274
+ {
275
+ name: 'ResourceId'
276
+ value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
277
+ }
278
+ {
279
+ name: 'DataModel'
280
+ value: {
281
+ version: '1.0.0'
282
+ timeContext: {
283
+ durationMs: 86400000
284
+ createdTime: '2018-05-04T23:42:40.072Z'
285
+ isInitialTime: false
286
+ grain: 1
287
+ useDashboardTimeRange: false
288
+ }
289
+ }
290
+ isOptional: true
291
+ }
292
+ {
293
+ name: 'ConfigurationId'
294
+ value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f'
295
+ isOptional: true
296
+ }
297
+ ]
298
+ #disable-next-line BCP036
299
+ type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart'
300
+ isAdapter: true
301
+ asset: {
302
+ idInputName: 'ResourceId'
303
+ type: 'ApplicationInsights'
304
+ }
305
+ defaultMenuItemId: 'failures'
306
+ }
307
+ }
308
+ {
309
+ position: {
310
+ x: 8
311
+ y: 1
312
+ colSpan: 3
313
+ rowSpan: 1
314
+ }
315
+ metadata: {
316
+ inputs: []
317
+ type: 'Extension/HubsExtension/PartType/MarkdownPart'
318
+ settings: {
319
+ content: {
320
+ settings: {
321
+ content: '# Responsiveness\r\n'
322
+ title: ''
323
+ subtitle: ''
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+ {
330
+ position: {
331
+ x: 11
332
+ y: 1
333
+ colSpan: 1
334
+ rowSpan: 1
335
+ }
336
+ metadata: {
337
+ inputs: [
338
+ {
339
+ name: 'ResourceId'
340
+ value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
341
+ }
342
+ {
343
+ name: 'DataModel'
344
+ value: {
345
+ version: '1.0.0'
346
+ timeContext: {
347
+ durationMs: 86400000
348
+ createdTime: '2018-05-04T23:43:37.804Z'
349
+ isInitialTime: false
350
+ grain: 1
351
+ useDashboardTimeRange: false
352
+ }
353
+ }
354
+ isOptional: true
355
+ }
356
+ {
357
+ name: 'ConfigurationId'
358
+ value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865'
359
+ isOptional: true
360
+ }
361
+ ]
362
+ #disable-next-line BCP036
363
+ type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart'
364
+ isAdapter: true
365
+ asset: {
366
+ idInputName: 'ResourceId'
367
+ type: 'ApplicationInsights'
368
+ }
369
+ defaultMenuItemId: 'performance'
370
+ }
371
+ }
372
+ {
373
+ position: {
374
+ x: 12
375
+ y: 1
376
+ colSpan: 3
377
+ rowSpan: 1
378
+ }
379
+ metadata: {
380
+ inputs: []
381
+ type: 'Extension/HubsExtension/PartType/MarkdownPart'
382
+ settings: {
383
+ content: {
384
+ settings: {
385
+ content: '# Browser'
386
+ title: ''
387
+ subtitle: ''
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ {
394
+ position: {
395
+ x: 15
396
+ y: 1
397
+ colSpan: 1
398
+ rowSpan: 1
399
+ }
400
+ metadata: {
401
+ inputs: [
402
+ {
403
+ name: 'ComponentId'
404
+ value: {
405
+ Name: applicationInsights.name
406
+ SubscriptionId: subscription().subscriptionId
407
+ ResourceGroup: resourceGroup().name
408
+ }
409
+ }
410
+ {
411
+ name: 'MetricsExplorerJsonDefinitionId'
412
+ value: 'BrowserPerformanceTimelineMetrics'
413
+ }
414
+ {
415
+ name: 'TimeContext'
416
+ value: {
417
+ durationMs: 86400000
418
+ createdTime: '2018-05-08T12:16:27.534Z'
419
+ isInitialTime: false
420
+ grain: 1
421
+ useDashboardTimeRange: false
422
+ }
423
+ }
424
+ {
425
+ name: 'CurrentFilter'
426
+ value: {
427
+ eventTypes: [
428
+ 4
429
+ 1
430
+ 3
431
+ 5
432
+ 2
433
+ 6
434
+ 13
435
+ ]
436
+ typeFacets: {}
437
+ isPermissive: false
438
+ }
439
+ }
440
+ {
441
+ name: 'id'
442
+ value: {
443
+ Name: applicationInsights.name
444
+ SubscriptionId: subscription().subscriptionId
445
+ ResourceGroup: resourceGroup().name
446
+ }
447
+ }
448
+ {
449
+ name: 'Version'
450
+ value: '1.0'
451
+ }
452
+ ]
453
+ #disable-next-line BCP036
454
+ type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart'
455
+ asset: {
456
+ idInputName: 'ComponentId'
457
+ type: 'ApplicationInsights'
458
+ }
459
+ defaultMenuItemId: 'browser'
460
+ }
461
+ }
462
+ {
463
+ position: {
464
+ x: 0
465
+ y: 2
466
+ colSpan: 4
467
+ rowSpan: 3
468
+ }
469
+ metadata: {
470
+ inputs: [
471
+ {
472
+ name: 'options'
473
+ value: {
474
+ chart: {
475
+ metrics: [
476
+ {
477
+ resourceMetadata: {
478
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
479
+ }
480
+ name: 'sessions/count'
481
+ aggregationType: 5
482
+ namespace: 'microsoft.insights/components/kusto'
483
+ metricVisualization: {
484
+ displayName: 'Sessions'
485
+ color: '#47BDF5'
486
+ }
487
+ }
488
+ {
489
+ resourceMetadata: {
490
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
491
+ }
492
+ name: 'users/count'
493
+ aggregationType: 5
494
+ namespace: 'microsoft.insights/components/kusto'
495
+ metricVisualization: {
496
+ displayName: 'Users'
497
+ color: '#7E58FF'
498
+ }
499
+ }
500
+ ]
501
+ title: 'Unique sessions and users'
502
+ visualization: {
503
+ chartType: 2
504
+ legendVisualization: {
505
+ isVisible: true
506
+ position: 2
507
+ hideSubtitle: false
508
+ }
509
+ axisVisualization: {
510
+ x: {
511
+ isVisible: true
512
+ axisType: 2
513
+ }
514
+ y: {
515
+ isVisible: true
516
+ axisType: 1
517
+ }
518
+ }
519
+ }
520
+ openBladeOnClick: {
521
+ openBlade: true
522
+ destinationBlade: {
523
+ extensionName: 'HubsExtension'
524
+ bladeName: 'ResourceMenuBlade'
525
+ parameters: {
526
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
527
+ menuid: 'segmentationUsers'
528
+ }
529
+ }
530
+ }
531
+ }
532
+ }
533
+ }
534
+ {
535
+ name: 'sharedTimeRange'
536
+ isOptional: true
537
+ }
538
+ ]
539
+ #disable-next-line BCP036
540
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
541
+ settings: {}
542
+ }
543
+ }
544
+ {
545
+ position: {
546
+ x: 4
547
+ y: 2
548
+ colSpan: 4
549
+ rowSpan: 3
550
+ }
551
+ metadata: {
552
+ inputs: [
553
+ {
554
+ name: 'options'
555
+ value: {
556
+ chart: {
557
+ metrics: [
558
+ {
559
+ resourceMetadata: {
560
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
561
+ }
562
+ name: 'requests/failed'
563
+ aggregationType: 7
564
+ namespace: 'microsoft.insights/components'
565
+ metricVisualization: {
566
+ displayName: 'Failed requests'
567
+ color: '#EC008C'
568
+ }
569
+ }
570
+ ]
571
+ title: 'Failed requests'
572
+ visualization: {
573
+ chartType: 3
574
+ legendVisualization: {
575
+ isVisible: true
576
+ position: 2
577
+ hideSubtitle: false
578
+ }
579
+ axisVisualization: {
580
+ x: {
581
+ isVisible: true
582
+ axisType: 2
583
+ }
584
+ y: {
585
+ isVisible: true
586
+ axisType: 1
587
+ }
588
+ }
589
+ }
590
+ openBladeOnClick: {
591
+ openBlade: true
592
+ destinationBlade: {
593
+ extensionName: 'HubsExtension'
594
+ bladeName: 'ResourceMenuBlade'
595
+ parameters: {
596
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
597
+ menuid: 'failures'
598
+ }
599
+ }
600
+ }
601
+ }
602
+ }
603
+ }
604
+ {
605
+ name: 'sharedTimeRange'
606
+ isOptional: true
607
+ }
608
+ ]
609
+ #disable-next-line BCP036
610
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
611
+ settings: {}
612
+ }
613
+ }
614
+ {
615
+ position: {
616
+ x: 8
617
+ y: 2
618
+ colSpan: 4
619
+ rowSpan: 3
620
+ }
621
+ metadata: {
622
+ inputs: [
623
+ {
624
+ name: 'options'
625
+ value: {
626
+ chart: {
627
+ metrics: [
628
+ {
629
+ resourceMetadata: {
630
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
631
+ }
632
+ name: 'requests/duration'
633
+ aggregationType: 4
634
+ namespace: 'microsoft.insights/components'
635
+ metricVisualization: {
636
+ displayName: 'Server response time'
637
+ color: '#00BCF2'
638
+ }
639
+ }
640
+ ]
641
+ title: 'Server response time'
642
+ visualization: {
643
+ chartType: 2
644
+ legendVisualization: {
645
+ isVisible: true
646
+ position: 2
647
+ hideSubtitle: false
648
+ }
649
+ axisVisualization: {
650
+ x: {
651
+ isVisible: true
652
+ axisType: 2
653
+ }
654
+ y: {
655
+ isVisible: true
656
+ axisType: 1
657
+ }
658
+ }
659
+ }
660
+ openBladeOnClick: {
661
+ openBlade: true
662
+ destinationBlade: {
663
+ extensionName: 'HubsExtension'
664
+ bladeName: 'ResourceMenuBlade'
665
+ parameters: {
666
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
667
+ menuid: 'performance'
668
+ }
669
+ }
670
+ }
671
+ }
672
+ }
673
+ }
674
+ {
675
+ name: 'sharedTimeRange'
676
+ isOptional: true
677
+ }
678
+ ]
679
+ #disable-next-line BCP036
680
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
681
+ settings: {}
682
+ }
683
+ }
684
+ {
685
+ position: {
686
+ x: 12
687
+ y: 2
688
+ colSpan: 4
689
+ rowSpan: 3
690
+ }
691
+ metadata: {
692
+ inputs: [
693
+ {
694
+ name: 'options'
695
+ value: {
696
+ chart: {
697
+ metrics: [
698
+ {
699
+ resourceMetadata: {
700
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
701
+ }
702
+ name: 'browserTimings/networkDuration'
703
+ aggregationType: 4
704
+ namespace: 'microsoft.insights/components'
705
+ metricVisualization: {
706
+ displayName: 'Page load network connect time'
707
+ color: '#7E58FF'
708
+ }
709
+ }
710
+ {
711
+ resourceMetadata: {
712
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
713
+ }
714
+ name: 'browserTimings/processingDuration'
715
+ aggregationType: 4
716
+ namespace: 'microsoft.insights/components'
717
+ metricVisualization: {
718
+ displayName: 'Client processing time'
719
+ color: '#44F1C8'
720
+ }
721
+ }
722
+ {
723
+ resourceMetadata: {
724
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
725
+ }
726
+ name: 'browserTimings/sendDuration'
727
+ aggregationType: 4
728
+ namespace: 'microsoft.insights/components'
729
+ metricVisualization: {
730
+ displayName: 'Send request time'
731
+ color: '#EB9371'
732
+ }
733
+ }
734
+ {
735
+ resourceMetadata: {
736
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
737
+ }
738
+ name: 'browserTimings/receiveDuration'
739
+ aggregationType: 4
740
+ namespace: 'microsoft.insights/components'
741
+ metricVisualization: {
742
+ displayName: 'Receiving response time'
743
+ color: '#0672F1'
744
+ }
745
+ }
746
+ ]
747
+ title: 'Average page load time breakdown'
748
+ visualization: {
749
+ chartType: 3
750
+ legendVisualization: {
751
+ isVisible: true
752
+ position: 2
753
+ hideSubtitle: false
754
+ }
755
+ axisVisualization: {
756
+ x: {
757
+ isVisible: true
758
+ axisType: 2
759
+ }
760
+ y: {
761
+ isVisible: true
762
+ axisType: 1
763
+ }
764
+ }
765
+ }
766
+ }
767
+ }
768
+ }
769
+ {
770
+ name: 'sharedTimeRange'
771
+ isOptional: true
772
+ }
773
+ ]
774
+ #disable-next-line BCP036
775
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
776
+ settings: {}
777
+ }
778
+ }
779
+ {
780
+ position: {
781
+ x: 0
782
+ y: 5
783
+ colSpan: 4
784
+ rowSpan: 3
785
+ }
786
+ metadata: {
787
+ inputs: [
788
+ {
789
+ name: 'options'
790
+ value: {
791
+ chart: {
792
+ metrics: [
793
+ {
794
+ resourceMetadata: {
795
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
796
+ }
797
+ name: 'availabilityResults/availabilityPercentage'
798
+ aggregationType: 4
799
+ namespace: 'microsoft.insights/components'
800
+ metricVisualization: {
801
+ displayName: 'Availability'
802
+ color: '#47BDF5'
803
+ }
804
+ }
805
+ ]
806
+ title: 'Average availability'
807
+ visualization: {
808
+ chartType: 3
809
+ legendVisualization: {
810
+ isVisible: true
811
+ position: 2
812
+ hideSubtitle: false
813
+ }
814
+ axisVisualization: {
815
+ x: {
816
+ isVisible: true
817
+ axisType: 2
818
+ }
819
+ y: {
820
+ isVisible: true
821
+ axisType: 1
822
+ }
823
+ }
824
+ }
825
+ openBladeOnClick: {
826
+ openBlade: true
827
+ destinationBlade: {
828
+ extensionName: 'HubsExtension'
829
+ bladeName: 'ResourceMenuBlade'
830
+ parameters: {
831
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
832
+ menuid: 'availability'
833
+ }
834
+ }
835
+ }
836
+ }
837
+ }
838
+ }
839
+ {
840
+ name: 'sharedTimeRange'
841
+ isOptional: true
842
+ }
843
+ ]
844
+ #disable-next-line BCP036
845
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
846
+ settings: {}
847
+ }
848
+ }
849
+ {
850
+ position: {
851
+ x: 4
852
+ y: 5
853
+ colSpan: 4
854
+ rowSpan: 3
855
+ }
856
+ metadata: {
857
+ inputs: [
858
+ {
859
+ name: 'options'
860
+ value: {
861
+ chart: {
862
+ metrics: [
863
+ {
864
+ resourceMetadata: {
865
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
866
+ }
867
+ name: 'exceptions/server'
868
+ aggregationType: 7
869
+ namespace: 'microsoft.insights/components'
870
+ metricVisualization: {
871
+ displayName: 'Server exceptions'
872
+ color: '#47BDF5'
873
+ }
874
+ }
875
+ {
876
+ resourceMetadata: {
877
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
878
+ }
879
+ name: 'dependencies/failed'
880
+ aggregationType: 7
881
+ namespace: 'microsoft.insights/components'
882
+ metricVisualization: {
883
+ displayName: 'Dependency failures'
884
+ color: '#7E58FF'
885
+ }
886
+ }
887
+ ]
888
+ title: 'Server exceptions and Dependency failures'
889
+ visualization: {
890
+ chartType: 2
891
+ legendVisualization: {
892
+ isVisible: true
893
+ position: 2
894
+ hideSubtitle: false
895
+ }
896
+ axisVisualization: {
897
+ x: {
898
+ isVisible: true
899
+ axisType: 2
900
+ }
901
+ y: {
902
+ isVisible: true
903
+ axisType: 1
904
+ }
905
+ }
906
+ }
907
+ }
908
+ }
909
+ }
910
+ {
911
+ name: 'sharedTimeRange'
912
+ isOptional: true
913
+ }
914
+ ]
915
+ #disable-next-line BCP036
916
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
917
+ settings: {}
918
+ }
919
+ }
920
+ {
921
+ position: {
922
+ x: 8
923
+ y: 5
924
+ colSpan: 4
925
+ rowSpan: 3
926
+ }
927
+ metadata: {
928
+ inputs: [
929
+ {
930
+ name: 'options'
931
+ value: {
932
+ chart: {
933
+ metrics: [
934
+ {
935
+ resourceMetadata: {
936
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
937
+ }
938
+ name: 'performanceCounters/processorCpuPercentage'
939
+ aggregationType: 4
940
+ namespace: 'microsoft.insights/components'
941
+ metricVisualization: {
942
+ displayName: 'Processor time'
943
+ color: '#47BDF5'
944
+ }
945
+ }
946
+ {
947
+ resourceMetadata: {
948
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
949
+ }
950
+ name: 'performanceCounters/processCpuPercentage'
951
+ aggregationType: 4
952
+ namespace: 'microsoft.insights/components'
953
+ metricVisualization: {
954
+ displayName: 'Process CPU'
955
+ color: '#7E58FF'
956
+ }
957
+ }
958
+ ]
959
+ title: 'Average processor and process CPU utilization'
960
+ visualization: {
961
+ chartType: 2
962
+ legendVisualization: {
963
+ isVisible: true
964
+ position: 2
965
+ hideSubtitle: false
966
+ }
967
+ axisVisualization: {
968
+ x: {
969
+ isVisible: true
970
+ axisType: 2
971
+ }
972
+ y: {
973
+ isVisible: true
974
+ axisType: 1
975
+ }
976
+ }
977
+ }
978
+ }
979
+ }
980
+ }
981
+ {
982
+ name: 'sharedTimeRange'
983
+ isOptional: true
984
+ }
985
+ ]
986
+ #disable-next-line BCP036
987
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
988
+ settings: {}
989
+ }
990
+ }
991
+ {
992
+ position: {
993
+ x: 12
994
+ y: 5
995
+ colSpan: 4
996
+ rowSpan: 3
997
+ }
998
+ metadata: {
999
+ inputs: [
1000
+ {
1001
+ name: 'options'
1002
+ value: {
1003
+ chart: {
1004
+ metrics: [
1005
+ {
1006
+ resourceMetadata: {
1007
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
1008
+ }
1009
+ name: 'exceptions/browser'
1010
+ aggregationType: 7
1011
+ namespace: 'microsoft.insights/components'
1012
+ metricVisualization: {
1013
+ displayName: 'Browser exceptions'
1014
+ color: '#47BDF5'
1015
+ }
1016
+ }
1017
+ ]
1018
+ title: 'Browser exceptions'
1019
+ visualization: {
1020
+ chartType: 2
1021
+ legendVisualization: {
1022
+ isVisible: true
1023
+ position: 2
1024
+ hideSubtitle: false
1025
+ }
1026
+ axisVisualization: {
1027
+ x: {
1028
+ isVisible: true
1029
+ axisType: 2
1030
+ }
1031
+ y: {
1032
+ isVisible: true
1033
+ axisType: 1
1034
+ }
1035
+ }
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ {
1041
+ name: 'sharedTimeRange'
1042
+ isOptional: true
1043
+ }
1044
+ ]
1045
+ #disable-next-line BCP036
1046
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
1047
+ settings: {}
1048
+ }
1049
+ }
1050
+ {
1051
+ position: {
1052
+ x: 0
1053
+ y: 8
1054
+ colSpan: 4
1055
+ rowSpan: 3
1056
+ }
1057
+ metadata: {
1058
+ inputs: [
1059
+ {
1060
+ name: 'options'
1061
+ value: {
1062
+ chart: {
1063
+ metrics: [
1064
+ {
1065
+ resourceMetadata: {
1066
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
1067
+ }
1068
+ name: 'availabilityResults/count'
1069
+ aggregationType: 7
1070
+ namespace: 'microsoft.insights/components'
1071
+ metricVisualization: {
1072
+ displayName: 'Availability test results count'
1073
+ color: '#47BDF5'
1074
+ }
1075
+ }
1076
+ ]
1077
+ title: 'Availability test results count'
1078
+ visualization: {
1079
+ chartType: 2
1080
+ legendVisualization: {
1081
+ isVisible: true
1082
+ position: 2
1083
+ hideSubtitle: false
1084
+ }
1085
+ axisVisualization: {
1086
+ x: {
1087
+ isVisible: true
1088
+ axisType: 2
1089
+ }
1090
+ y: {
1091
+ isVisible: true
1092
+ axisType: 1
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ {
1100
+ name: 'sharedTimeRange'
1101
+ isOptional: true
1102
+ }
1103
+ ]
1104
+ #disable-next-line BCP036
1105
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
1106
+ settings: {}
1107
+ }
1108
+ }
1109
+ {
1110
+ position: {
1111
+ x: 4
1112
+ y: 8
1113
+ colSpan: 4
1114
+ rowSpan: 3
1115
+ }
1116
+ metadata: {
1117
+ inputs: [
1118
+ {
1119
+ name: 'options'
1120
+ value: {
1121
+ chart: {
1122
+ metrics: [
1123
+ {
1124
+ resourceMetadata: {
1125
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
1126
+ }
1127
+ name: 'performanceCounters/processIOBytesPerSecond'
1128
+ aggregationType: 4
1129
+ namespace: 'microsoft.insights/components'
1130
+ metricVisualization: {
1131
+ displayName: 'Process IO rate'
1132
+ color: '#47BDF5'
1133
+ }
1134
+ }
1135
+ ]
1136
+ title: 'Average process I/O rate'
1137
+ visualization: {
1138
+ chartType: 2
1139
+ legendVisualization: {
1140
+ isVisible: true
1141
+ position: 2
1142
+ hideSubtitle: false
1143
+ }
1144
+ axisVisualization: {
1145
+ x: {
1146
+ isVisible: true
1147
+ axisType: 2
1148
+ }
1149
+ y: {
1150
+ isVisible: true
1151
+ axisType: 1
1152
+ }
1153
+ }
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+ {
1159
+ name: 'sharedTimeRange'
1160
+ isOptional: true
1161
+ }
1162
+ ]
1163
+ #disable-next-line BCP036
1164
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
1165
+ settings: {}
1166
+ }
1167
+ }
1168
+ {
1169
+ position: {
1170
+ x: 8
1171
+ y: 8
1172
+ colSpan: 4
1173
+ rowSpan: 3
1174
+ }
1175
+ metadata: {
1176
+ inputs: [
1177
+ {
1178
+ name: 'options'
1179
+ value: {
1180
+ chart: {
1181
+ metrics: [
1182
+ {
1183
+ resourceMetadata: {
1184
+ id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}'
1185
+ }
1186
+ name: 'performanceCounters/memoryAvailableBytes'
1187
+ aggregationType: 4
1188
+ namespace: 'microsoft.insights/components'
1189
+ metricVisualization: {
1190
+ displayName: 'Available memory'
1191
+ color: '#47BDF5'
1192
+ }
1193
+ }
1194
+ ]
1195
+ title: 'Average available memory'
1196
+ visualization: {
1197
+ chartType: 2
1198
+ legendVisualization: {
1199
+ isVisible: true
1200
+ position: 2
1201
+ hideSubtitle: false
1202
+ }
1203
+ axisVisualization: {
1204
+ x: {
1205
+ isVisible: true
1206
+ axisType: 2
1207
+ }
1208
+ y: {
1209
+ isVisible: true
1210
+ axisType: 1
1211
+ }
1212
+ }
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1217
+ {
1218
+ name: 'sharedTimeRange'
1219
+ isOptional: true
1220
+ }
1221
+ ]
1222
+ #disable-next-line BCP036
1223
+ type: 'Extension/HubsExtension/PartType/MonitorChartPart'
1224
+ settings: {}
1225
+ }
1226
+ }
1227
+ ]
1228
+ }
1229
+ ]
1230
+ }
1231
+ }
1232
+
1233
+ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = {
1234
+ name: applicationInsightsName
1235
+ }
infra/core/monitor/applicationinsights.bicep ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param dashboardName string
3
+ param location string = resourceGroup().location
4
+ param tags object = {}
5
+
6
+ param logAnalyticsWorkspaceId string
7
+
8
+ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
9
+ name: name
10
+ location: location
11
+ tags: tags
12
+ kind: 'web'
13
+ properties: {
14
+ Application_Type: 'web'
15
+ WorkspaceResourceId: logAnalyticsWorkspaceId
16
+ }
17
+ }
18
+
19
+ module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = {
20
+ name: 'application-insights-dashboard'
21
+ params: {
22
+ name: dashboardName
23
+ location: location
24
+ applicationInsightsName: applicationInsights.name
25
+ }
26
+ }
27
+
28
+ output connectionString string = applicationInsights.properties.ConnectionString
29
+ output instrumentationKey string = applicationInsights.properties.InstrumentationKey
30
+ output name string = applicationInsights.name
infra/core/monitor/loganalytics.bicep ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
6
+ name: name
7
+ location: location
8
+ tags: tags
9
+ properties: any({
10
+ retentionInDays: 30
11
+ features: {
12
+ searchVersion: 1
13
+ }
14
+ sku: {
15
+ name: 'PerGB2018'
16
+ }
17
+ })
18
+ }
19
+
20
+ output id string = logAnalytics.id
21
+ output name string = logAnalytics.name
infra/core/monitor/monitoring.bicep ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param logAnalyticsName string
2
+ param applicationInsightsName string
3
+ param applicationInsightsDashboardName string
4
+ param location string = resourceGroup().location
5
+ param tags object = {}
6
+
7
+ module logAnalytics 'loganalytics.bicep' = {
8
+ name: 'loganalytics'
9
+ params: {
10
+ name: logAnalyticsName
11
+ location: location
12
+ tags: tags
13
+ }
14
+ }
15
+
16
+ module applicationInsights 'applicationinsights.bicep' = {
17
+ name: 'applicationinsights'
18
+ params: {
19
+ name: applicationInsightsName
20
+ location: location
21
+ tags: tags
22
+ dashboardName: applicationInsightsDashboardName
23
+ logAnalyticsWorkspaceId: logAnalytics.outputs.id
24
+ }
25
+ }
26
+
27
+ output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString
28
+ output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey
29
+ output applicationInsightsName string = applicationInsights.outputs.name
30
+ output logAnalyticsWorkspaceId string = logAnalytics.outputs.id
31
+ output logAnalyticsWorkspaceName string = logAnalytics.outputs.name
infra/core/security/dnszone.bicep ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param dnsHostName
2
+ param virtualNetwork object
3
+
4
+ resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
5
+ name: dnsHostName
6
+ location: 'global'
7
+ tags: tags
8
+ dependsOn: [
9
+ virtualNetwork
10
+ ]
11
+ }
12
+
13
+ resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
14
+ parent: privateDnsZone
15
+ name: '${dnsHostName}-link'
16
+ location: 'global'
17
+ properties: {
18
+ registrationEnabled: false
19
+ virtualNetwork: {
20
+ id: virtualNetwork.id
21
+ }
22
+ }
23
+ }
infra/core/security/keyvault-access.bicep ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string = 'add'
2
+
3
+ param keyVaultName string = ''
4
+ param permissions object = { secrets: [ 'get', 'list' ] }
5
+ param principalId string
6
+
7
+ resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = {
8
+ parent: keyVault
9
+ name: name
10
+ properties: {
11
+ accessPolicies: [ {
12
+ objectId: principalId
13
+ tenantId: subscription().tenantId
14
+ permissions: permissions
15
+ } ]
16
+ }
17
+ }
18
+
19
+ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
20
+ name: keyVaultName
21
+ }
infra/core/security/keyvault-secret.bicep ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param tags object = {}
3
+ param keyVaultName string
4
+ param contentType string = 'string'
5
+ @description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates')
6
+ @secure()
7
+ param secretValue string
8
+
9
+ param enabled bool = true
10
+ param exp int = 0
11
+ param nbf int = 0
12
+
13
+ resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
14
+ name: name
15
+ tags: tags
16
+ parent: keyVault
17
+ properties: {
18
+ attributes: {
19
+ enabled: enabled
20
+ exp: exp
21
+ nbf: nbf
22
+ }
23
+ contentType: contentType
24
+ value: secretValue
25
+ }
26
+ }
27
+
28
+ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
29
+ name: keyVaultName
30
+ }
infra/core/security/keyvault.bicep ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param principalId string = ''
6
+
7
+ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = {
8
+ name: name
9
+ location: location
10
+ tags: tags
11
+ properties: {
12
+ tenantId: subscription().tenantId
13
+ sku: { family: 'A', name: 'standard' }
14
+ accessPolicies: !empty(principalId) ? [
15
+ {
16
+ objectId: principalId
17
+ permissions: { secrets: [ 'get', 'list' ] }
18
+ tenantId: subscription().tenantId
19
+ }
20
+ ] : []
21
+ }
22
+ }
23
+
24
+ output endpoint string = keyVault.properties.vaultUri
25
+ output name string = keyVault.name
infra/core/security/virtualnetwork.bicep ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param postgresServerName string
6
+
7
+ var databaseSubnetName = 'database-subnet'
8
+ var webappSubnetName = 'webapp-subnet'
9
+
10
+ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
11
+ name: name
12
+ location: location
13
+ tags: tags
14
+ properties: {
15
+ addressSpace: {
16
+ addressPrefixes: [
17
+ '10.0.0.0/16'
18
+ ]
19
+ }
20
+ subnets: [
21
+ {
22
+ name: databaseSubnetName
23
+ properties: {
24
+ addressPrefix: '10.0.0.0/24'
25
+ delegations: [
26
+ {
27
+ name: '${name}-subnet-db'
28
+ properties: {
29
+ serviceName: 'Microsoft.DBforPostgreSQL/flexibleServers'
30
+ }
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ {
36
+ name: webappSubnetName
37
+ properties: {
38
+ addressPrefix: '10.0.1.0/24'
39
+ delegations: [
40
+ {
41
+ name: '${name}-subnet-web'
42
+ properties: {
43
+ serviceName: 'Microsoft.Web/serverFarms'
44
+ }
45
+ }
46
+ ]
47
+ }
48
+ }
49
+ ]
50
+ }
51
+ resource databaseSubnet 'subnets' existing = {
52
+ name: databaseSubnetName
53
+ }
54
+ resource webappSubnet 'subnets' existing = {
55
+ name: webappSubnetName
56
+ }
57
+ }
58
+
59
+ resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
60
+ name: '${postgresServerName}.private.postgres.database.azure.com'
61
+ location: 'global'
62
+ tags: tags
63
+ dependsOn: [
64
+ virtualNetwork
65
+ ]
66
+ }
67
+
68
+ resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
69
+ parent: privateDnsZone
70
+ name: '${name}-link'
71
+ location: 'global'
72
+ properties: {
73
+ registrationEnabled: false
74
+ virtualNetwork: {
75
+ id: virtualNetwork.id
76
+ }
77
+ }
78
+ }
79
+
80
+ output databaseSubnetId string = virtualNetwork::databaseSubnet.id
81
+ output webSubnetId string = virtualNetwork::webappSubnet.id
82
+ output privateDnsZoneId string = privateDnsZone.id
83
+ output privateDnsZoneLink object = privateDnsZoneLink
infra/core/storage/storage-account.bicep ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ param name string
2
+ param location string = resourceGroup().location
3
+ param tags object = {}
4
+
5
+ param allowBlobPublicAccess bool = false
6
+ param containers array = []
7
+ param kind string = 'StorageV2'
8
+ param minimumTlsVersion string = 'TLS1_2'
9
+ param sku object = { name: 'Standard_LRS' }
10
+
11
+ resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = {
12
+ name: name
13
+ location: location
14
+ tags: tags
15
+ kind: kind
16
+ sku: sku
17
+ properties: {
18
+ minimumTlsVersion: minimumTlsVersion
19
+ allowBlobPublicAccess: allowBlobPublicAccess
20
+ networkAcls: {
21
+ bypass: 'AzureServices'
22
+ defaultAction: 'Allow'
23
+ }
24
+ }
25
+
26
+ resource blobServices 'blobServices' = if (!empty(containers)) {
27
+ name: 'default'
28
+ resource container 'containers' = [for container in containers: {
29
+ name: container.name
30
+ properties: {
31
+ publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None'
32
+ }
33
+ }]
34
+ }
35
+ }
36
+
37
+ output name string = storage.name
38
+ output primaryEndpoints object = storage.properties.primaryEndpoints
infra/main.bicep CHANGED
@@ -22,16 +22,99 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
22
  tags: tags
23
  }
24
 
25
- module resources 'resources.bicep' = {
26
- name: 'resources'
 
 
 
 
27
  scope: resourceGroup
28
  params: {
29
- name: name
30
  location: location
31
- resourceToken: resourceToken
32
  tags: tags
33
- databasePassword: databasePassword
34
  }
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  output AZURE_LOCATION string = location
 
22
  tags: tags
23
  }
24
 
25
+ var prefix = '${name}-${resourceToken}'
26
+
27
+ var postgresServerName = '${prefix}-postgresql'
28
+
29
+ module virtualNetwork 'core/security/virtualnetwork.bicep' = {
30
+ name: 'virtualnetwork'
31
  scope: resourceGroup
32
  params: {
33
+ name: '${prefix}-vnet'
34
  location: location
 
35
  tags: tags
36
+ postgresServerName: postgresServerName
37
  }
38
  }
39
 
40
+ var databaseName = 'django'
41
+ var databaseUser = 'django'
42
+
43
+ module postgresServer 'core/database/postgresql/flexibleserver.bicep' = {
44
+ name: 'postgresql'
45
+ scope: resourceGroup
46
+ params: {
47
+ name: postgresServerName
48
+ location: location
49
+ tags: tags
50
+ sku: {
51
+ name: 'Standard_B1ms'
52
+ tier: 'Burstable'
53
+ }
54
+ storage: {
55
+ storageSizeGB: 32
56
+ }
57
+ version: '13'
58
+ administratorLogin: databaseUser
59
+ administratorLoginPassword: databasePassword
60
+ databaseName: databaseName
61
+ delegatedSubnetResourceId: virtualNetwork.outputs.databaseSubnetId
62
+ privateDnsZoneArmResourceId: virtualNetwork.outputs.privateDnsZoneId
63
+ privateDnsZoneLink: virtualNetwork.outputs.privateDnsZoneLink
64
+ }
65
+ }
66
+
67
+ module web 'core/host/appservice.bicep' = {
68
+ name: 'appservice'
69
+ scope: resourceGroup
70
+ params: {
71
+ name: '${prefix}-appservice'
72
+ location: location
73
+ tags: union(tags, { 'azd-service-name': 'web' })
74
+ appServicePlanId: appServicePlan.outputs.id
75
+ runtimeName: 'python'
76
+ runtimeVersion: '3.9'
77
+ scmDoBuildDuringDeployment: true
78
+ ftpsState: 'Disabled'
79
+ managedIdentity: true
80
+ 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'
81
+ virtualNetwork: virtualNetwork
82
+ subnetResourceId: virtualNetwork.outputs.webSubnetId
83
+ appSettings: {
84
+ DBHOST: postgresServerName
85
+ DBNAME: databaseName
86
+ DBUSER: databaseUser
87
+ DBPASS: databasePassword
88
+ }
89
+ }
90
+ }
91
+
92
+
93
+ module appServicePlan 'core/host/appserviceplan.bicep' = {
94
+ name: 'serviceplan'
95
+ scope: resourceGroup
96
+ params: {
97
+ name: '${prefix}-serviceplan'
98
+ location: location
99
+ tags: tags
100
+ sku: {
101
+ name: 'B1'
102
+ }
103
+ reserved: true
104
+ }
105
+ }
106
+
107
+ module logAnalyticsWorkspace 'core/monitor/loganalytics.bicep' = {
108
+ name: 'loganalytics'
109
+ scope: resourceGroup
110
+ params: {
111
+ name: '${prefix}-loganalytics'
112
+ location: location
113
+ tags: tags
114
+ }
115
+ }
116
+
117
+
118
+
119
+ output WEB_URI string = 'https://${web.outputs.uri}'
120
  output AZURE_LOCATION string = location
infra/resources.bicep DELETED
@@ -1,222 +0,0 @@
1
- param name string
2
- param location string
3
- param resourceToken string
4
- @secure()
5
- param databasePassword string
6
- param tags object
7
-
8
- var prefix = '${name}-${resourceToken}'
9
-
10
- var pgServerName = '${prefix}-postgres-server'
11
- var databaseSubnetName = 'database-subnet'
12
- var webappSubnetName = 'webapp-subnet'
13
-
14
- resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
15
- name: '${prefix}-vnet'
16
- location: location
17
- tags: tags
18
- properties: {
19
- addressSpace: {
20
- addressPrefixes: [
21
- '10.0.0.0/16'
22
- ]
23
- }
24
- subnets: [
25
- {
26
- name: databaseSubnetName
27
- properties: {
28
- addressPrefix: '10.0.0.0/24'
29
- delegations: [
30
- {
31
- name: '${prefix}-subnet-delegation'
32
- properties: {
33
- serviceName: 'Microsoft.DBforPostgreSQL/flexibleServers'
34
- }
35
- }
36
- ]
37
- }
38
- }
39
- {
40
- name: webappSubnetName
41
- properties: {
42
- addressPrefix: '10.0.1.0/24'
43
- delegations: [
44
- {
45
- name: '${prefix}-subnet-delegation-web'
46
- properties: {
47
- serviceName: 'Microsoft.Web/serverFarms'
48
- }
49
- }
50
- ]
51
- }
52
- }
53
- ]
54
- }
55
- resource databaseSubnet 'subnets' existing = {
56
- name: databaseSubnetName
57
- }
58
- resource webappSubnet 'subnets' existing = {
59
- name: webappSubnetName
60
- }
61
- }
62
-
63
- resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
64
- name: '${pgServerName}.private.postgres.database.azure.com'
65
- location: 'global'
66
- tags: tags
67
- dependsOn: [
68
- virtualNetwork
69
- ]
70
- }
71
-
72
- resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
73
- parent: privateDnsZone
74
- name: '${pgServerName}-link'
75
- location: 'global'
76
- properties: {
77
- registrationEnabled: false
78
- virtualNetwork: {
79
- id: virtualNetwork.id
80
- }
81
- }
82
- }
83
-
84
- resource web 'Microsoft.Web/sites@2022-03-01' = {
85
- name: '${prefix}-app-service'
86
- location: location
87
- tags: union(tags, { 'azd-service-name': 'web' })
88
- kind: 'app,linux'
89
- properties: {
90
- serverFarmId: appServicePlan.id
91
- siteConfig: {
92
- alwaysOn: true
93
- linuxFxVersion: 'PYTHON|3.10'
94
- ftpsState: 'Disabled'
95
- 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'
96
- }
97
- httpsOnly: true
98
- }
99
- identity: {
100
- type: 'SystemAssigned'
101
- }
102
-
103
- resource appSettings 'config' = {
104
- name: 'appsettings'
105
- properties: {
106
- DBHOST: postgresServer.name
107
- DBNAME: djangoDatabase.name
108
- DBUSER: postgresServer.properties.administratorLogin
109
- DBPASS: databasePassword
110
- SCM_DO_BUILD_DURING_DEPLOYMENT: 'true'
111
- }
112
- }
113
-
114
- resource logs 'config' = {
115
- name: 'logs'
116
- properties: {
117
- applicationLogs: {
118
- fileSystem: {
119
- level: 'Verbose'
120
- }
121
- }
122
- detailedErrorMessages: {
123
- enabled: true
124
- }
125
- failedRequestsTracing: {
126
- enabled: true
127
- }
128
- httpLogs: {
129
- fileSystem: {
130
- enabled: true
131
- retentionInDays: 1
132
- retentionInMb: 35
133
- }
134
- }
135
- }
136
- }
137
-
138
- resource webappVnetConfig 'networkConfig' = {
139
- name: 'virtualNetwork'
140
- properties: {
141
- subnetResourceId: virtualNetwork::webappSubnet.id
142
- }
143
- }
144
-
145
- dependsOn: [virtualNetwork]
146
-
147
- }
148
-
149
- resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
150
- name: '${prefix}-service-plan'
151
- location: location
152
- tags: tags
153
- sku: {
154
- name: 'B1'
155
- }
156
- properties: {
157
- reserved: true
158
- }
159
- }
160
-
161
- resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-03-01-preview' = {
162
- name: '${prefix}-workspace'
163
- location: location
164
- tags: tags
165
- properties: any({
166
- retentionInDays: 30
167
- features: {
168
- searchVersion: 1
169
- }
170
- sku: {
171
- name: 'PerGB2018'
172
- }
173
- })
174
- }
175
-
176
- resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = {
177
- location: location
178
- tags: tags
179
- name: pgServerName
180
- sku: {
181
- name: 'Standard_B1ms'
182
- tier: 'Burstable'
183
- }
184
- properties: {
185
- version: '13'
186
- administratorLogin: 'django'
187
- administratorLoginPassword: databasePassword
188
- availabilityZone: '1'
189
- storage: {
190
- storageSizeGB: 32
191
- }
192
- backup: {
193
- backupRetentionDays: 7
194
- geoRedundantBackup: 'Disabled'
195
- }
196
- network: {
197
- delegatedSubnetResourceId: virtualNetwork::databaseSubnet.id
198
- privateDnsZoneArmResourceId: privateDnsZone.id
199
- }
200
- highAvailability: {
201
- mode: 'Disabled'
202
- }
203
- maintenanceWindow: {
204
- customWindow: 'Disabled'
205
- dayOfWeek: 0
206
- startHour: 0
207
- startMinute: 0
208
- }
209
- }
210
-
211
- dependsOn: [
212
- privateDnsZoneLink
213
- ]
214
- }
215
-
216
-
217
- resource djangoDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2022-01-20-preview' = {
218
- parent: postgresServer
219
- name: 'django'
220
- }
221
-
222
- output WEB_URI string = 'https://${web.properties.defaultHostName}'